// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {recentGraphsStorage, type RecentGraphEntry} from './recent_graphs';

describe('RecentGraphsStorage', () => {
  beforeEach(() => {
    // Clear localStorage before each test
    window.localStorage.clear();
    // Reset the storage instance
    recentGraphsStorage.data = [];
  });

  // Helper to create a mock graph entry
  function createEntry(
    nodeCount: number,
    options?: Partial<RecentGraphEntry>,
  ): RecentGraphEntry {
    return {
      name: options?.name ?? `Graph ${nodeCount}`,
      json: options?.json ?? `{"nodes": ${nodeCount}}`,
      timestamp: options?.timestamp ?? Date.now(),
      nodeCount,
      labelCount: options?.labelCount ?? 0,
      starred: options?.starred ?? false,
    };
  }

  describe('data getter/setter', () => {
    test('should start with empty data after clear', () => {
      expect(recentGraphsStorage.data.length).toBe(0);
    });

    test('should allow setting data directly', () => {
      recentGraphsStorage.data = [createEntry(3)];
      expect(recentGraphsStorage.data.length).toBe(1);
      expect(recentGraphsStorage.data[0].nodeCount).toBe(3);
    });
  });

  describe('finalizeCurrentGraph', () => {
    test('should do nothing when there is no working graph', () => {
      recentGraphsStorage.finalizeCurrentGraph();
      expect(recentGraphsStorage.data.length).toBe(0);
    });

    test('should do nothing when working graph has zero nodes', () => {
      recentGraphsStorage.data = [createEntry(0)];
      recentGraphsStorage.finalizeCurrentGraph();
      // Should still be just one entry (unchanged)
      expect(recentGraphsStorage.data.length).toBe(1);
    });

    test('should finalize current graph and create new working slot', () => {
      recentGraphsStorage.data = [createEntry(3)];
      expect(recentGraphsStorage.data.length).toBe(1);

      recentGraphsStorage.finalizeCurrentGraph();

      // Should now have 2 entries: new working slot at 0, finalized graph at 1
      expect(recentGraphsStorage.data.length).toBe(2);
      expect(recentGraphsStorage.data[0].nodeCount).toBe(0); // New empty working slot
      expect(recentGraphsStorage.data[1].nodeCount).toBe(3); // Finalized graph
    });

    test('should enforce maxItems limit for unstarred graphs', () => {
      const originalMaxItems = recentGraphsStorage.maxItems;
      recentGraphsStorage.maxItems = 3;

      // Set up 3 existing finalized graphs (index 0 is working slot)
      recentGraphsStorage.data = [
        createEntry(4), // Working slot with content
        createEntry(3),
        createEntry(2),
        createEntry(1),
      ];

      // Finalize current - should remove oldest unstarred
      recentGraphsStorage.finalizeCurrentGraph();

      // Count unstarred items with content
      const unstarredWithContent = recentGraphsStorage.data.filter(
        (e) => !e.starred && (e.nodeCount ?? 0) > 0,
      );

      // Should have at most maxItems unstarred graphs with content
      expect(unstarredWithContent.length).toBeLessThanOrEqual(3);

      recentGraphsStorage.maxItems = originalMaxItems;
    });

    test('should not count starred graphs toward maxItems limit', () => {
      const originalMaxItems = recentGraphsStorage.maxItems;
      recentGraphsStorage.maxItems = 2;

      // Set up data with one starred graph and working slot
      recentGraphsStorage.data = [
        createEntry(4), // Working slot with content
        createEntry(3, {starred: true}), // Starred - should be preserved
        createEntry(2),
        createEntry(1),
      ];

      recentGraphsStorage.finalizeCurrentGraph();

      // Count starred and unstarred with content
      const starred = recentGraphsStorage.data.filter(
        (e) => e.starred && (e.nodeCount ?? 0) > 0,
      );
      const unstarred = recentGraphsStorage.data.filter(
        (e) => !e.starred && (e.nodeCount ?? 0) > 0,
      );

      // Should have 1 starred graph preserved
      expect(starred.length).toBe(1);

      // Should have at most maxItems unstarred graphs
      expect(unstarred.length).toBeLessThanOrEqual(2);

      recentGraphsStorage.maxItems = originalMaxItems;
    });
  });

  describe('getCurrentJson', () => {
    test('should return undefined when no graphs exist', () => {
      expect(recentGraphsStorage.getCurrentJson()).toBeUndefined();
    });

    test('should return undefined when only empty working slot exists', () => {
      recentGraphsStorage.data = [createEntry(0)];
      expect(recentGraphsStorage.getCurrentJson()).toBeUndefined();
    });

    test('should return first graph with content', () => {
      recentGraphsStorage.data = [createEntry(2, {json: '{"test": true}'})];

      const json = recentGraphsStorage.getCurrentJson();
      expect(json).toBe('{"test": true}');
    });

    test('should skip empty working slot and return finalized graph', () => {
      recentGraphsStorage.data = [
        createEntry(0), // Empty working slot
        createEntry(3, {json: '{"finalized": true}'}),
      ];

      const json = recentGraphsStorage.getCurrentJson();
      expect(json).toBe('{"finalized": true}');
    });
  });

  describe('setStarred', () => {
    test('should star a graph', () => {
      recentGraphsStorage.data = [createEntry(2)];
      recentGraphsStorage.setStarred(0, true);

      expect(recentGraphsStorage.data[0].starred).toBe(true);
    });

    test('should unstar a graph', () => {
      recentGraphsStorage.data = [createEntry(2, {starred: true})];
      recentGraphsStorage.setStarred(0, false);

      expect(recentGraphsStorage.data[0].starred).toBe(false);
    });
  });

  describe('rename', () => {
    test('should rename a graph', () => {
      recentGraphsStorage.data = [createEntry(2)];
      recentGraphsStorage.rename(0, 'My Custom Name');

      expect(recentGraphsStorage.data[0].name).toBe('My Custom Name');
    });

    test('should trim whitespace from name', () => {
      recentGraphsStorage.data = [createEntry(2)];
      recentGraphsStorage.rename(0, '  Trimmed Name  ');

      expect(recentGraphsStorage.data[0].name).toBe('Trimmed Name');
    });

    test('should keep original name if new name is empty', () => {
      recentGraphsStorage.data = [createEntry(2, {name: 'Original'})];
      recentGraphsStorage.rename(0, '   ');

      expect(recentGraphsStorage.data[0].name).toBe('Original');
    });
  });

  describe('getJson', () => {
    test('should return undefined for invalid index', () => {
      expect(recentGraphsStorage.getJson(-1)).toBeUndefined();
      expect(recentGraphsStorage.getJson(0)).toBeUndefined();
      expect(recentGraphsStorage.getJson(100)).toBeUndefined();
    });

    test('should return json for valid index', () => {
      recentGraphsStorage.data = [createEntry(2, {json: '{"test": 123}'})];
      const json = recentGraphsStorage.getJson(0);

      expect(json).toBe('{"test": 123}');
    });
  });

  describe('remove', () => {
    test('should remove a graph from history', () => {
      recentGraphsStorage.data = [
        createEntry(0), // Working slot
        createEntry(3),
        createEntry(2),
        createEntry(1),
      ];

      const initialLength = recentGraphsStorage.data.length;
      recentGraphsStorage.remove(2); // Remove middle entry

      expect(recentGraphsStorage.data.length).toBe(initialLength - 1);
      expect(recentGraphsStorage.data[2].nodeCount).toBe(1); // Last entry moved up
    });

    test('should remove first entry', () => {
      recentGraphsStorage.data = [createEntry(1), createEntry(2)];
      recentGraphsStorage.remove(0);

      expect(recentGraphsStorage.data.length).toBe(1);
      expect(recentGraphsStorage.data[0].nodeCount).toBe(2);
    });

    test('should remove last entry', () => {
      recentGraphsStorage.data = [createEntry(1), createEntry(2)];
      recentGraphsStorage.remove(1);

      expect(recentGraphsStorage.data.length).toBe(1);
      expect(recentGraphsStorage.data[0].nodeCount).toBe(1);
    });
  });

  describe('clear', () => {
    test('should clear all data', () => {
      recentGraphsStorage.data = [
        createEntry(1),
        createEntry(2),
        createEntry(3),
      ];
      recentGraphsStorage.clear();

      expect(recentGraphsStorage.data.length).toBe(0);
    });
  });

  describe('generateName', () => {
    test('should generate a name with date and time', () => {
      const name = recentGraphsStorage.generateName();

      // Should contain "Graph" and some date/time info
      expect(name).toContain('Graph');
      // Should match pattern like "Graph Jan 15 14:30"
      expect(name).toMatch(/Graph \w+ \d+ \d{2}:\d{2}/);
    });
  });

  describe('persistence', () => {
    test('should persist graphs to localStorage', () => {
      recentGraphsStorage.data = [createEntry(2)];
      // Trigger save by calling a mutating method
      recentGraphsStorage.setStarred(0, true);

      const stored = window.localStorage.getItem('recentExploreGraphs');
      expect(stored).not.toBeNull();

      const parsed = JSON.parse(stored!);
      expect(parsed.length).toBe(1);
      expect(parsed[0].nodeCount).toBe(2);
      expect(parsed[0].starred).toBe(true);
    });

    test('should persist renamed graph', () => {
      recentGraphsStorage.data = [createEntry(2)];
      recentGraphsStorage.rename(0, 'Custom Name');

      const stored = window.localStorage.getItem('recentExploreGraphs');
      const parsed = JSON.parse(stored!);
      expect(parsed[0].name).toBe('Custom Name');
    });

    test('should persist removal', () => {
      recentGraphsStorage.data = [createEntry(1), createEntry(2)];
      recentGraphsStorage.remove(0);

      const stored = window.localStorage.getItem('recentExploreGraphs');
      const parsed = JSON.parse(stored!);
      expect(parsed.length).toBe(1);
      expect(parsed[0].nodeCount).toBe(2);
    });
  });

  describe('historical graphs ordering', () => {
    test('starred graphs should be identifiable in data', () => {
      recentGraphsStorage.data = [
        createEntry(0), // Working slot
        createEntry(1, {starred: false}),
        createEntry(2, {starred: true}),
        createEntry(3, {starred: false}),
      ];

      const starred = recentGraphsStorage.data.filter(
        (e, i) => i > 0 && e.starred,
      );
      const unstarred = recentGraphsStorage.data.filter(
        (e, i) => i > 0 && !e.starred,
      );

      expect(starred.length).toBe(1);
      expect(starred[0].nodeCount).toBe(2);
      expect(unstarred.length).toBe(2);
    });
  });
});
