import {
  sortPayloadByValueDesc,
  toggleInSet,
  setIdsHidden,
  groupVisibilityState,
  groupLines,
  serializePlotConfig,
  readPlotConfig,
} from '../src/lib/crossSubjectChart';

describe('sortPayloadByValueDesc', () => {
  it('sorts by value descending and drops null/undefined values', () => {
    const payload = [
      { dataKey: 'a', value: 40 },
      { dataKey: 'b', value: null },
      { dataKey: 'c', value: 92 },
      { dataKey: 'd', value: 78 },
    ];
    expect(sortPayloadByValueDesc(payload).map((p) => p.dataKey)).toEqual(['c', 'd', 'a']);
  });
  it('returns [] for non-array input', () => {
    expect(sortPayloadByValueDesc(undefined)).toEqual([]);
  });
  it('does not mutate the input array', () => {
    const payload = [{ dataKey: 'a', value: 1 }, { dataKey: 'b', value: 2 }];
    sortPayloadByValueDesc(payload);
    expect(payload.map((p) => p.dataKey)).toEqual(['a', 'b']);
  });
});

describe('toggleInSet', () => {
  it('adds a missing id and removes a present id, returning a new Set', () => {
    const a = new Set(['x']);
    const added = toggleInSet(a, 'y');
    expect([...added].sort()).toEqual(['x', 'y']);
    expect(added).not.toBe(a);
    const removed = toggleInSet(added, 'x');
    expect([...removed]).toEqual(['y']);
  });
});

describe('setIdsHidden', () => {
  it('adds all ids when hidden=true', () => {
    expect([...setIdsHidden(new Set(['a']), ['b', 'c'], true)].sort()).toEqual(['a', 'b', 'c']);
  });
  it('removes all ids when hidden=false', () => {
    expect([...setIdsHidden(new Set(['a', 'b', 'c']), ['b', 'c'], false)]).toEqual(['a']);
  });
  it('returns a new Set', () => {
    const s = new Set();
    expect(setIdsHidden(s, ['a'], true)).not.toBe(s);
  });
});

describe('groupVisibilityState', () => {
  it("returns 'all' when none of the ids are hidden", () => {
    expect(groupVisibilityState(['a', 'b'], new Set())).toBe('all');
  });
  it("returns 'none' when all ids are hidden", () => {
    expect(groupVisibilityState(['a', 'b'], new Set(['a', 'b']))).toBe('none');
  });
  it("returns 'some' when a subset is hidden", () => {
    expect(groupVisibilityState(['a', 'b'], new Set(['a']))).toBe('some');
  });
  it("returns 'none' for an empty group", () => {
    expect(groupVisibilityState([], new Set())).toBe('none');
  });
});

describe('groupLines', () => {
  it('groups by .group preserving first-seen group order and within-group order', () => {
    const lines = [
      { id: '1', group: 'B' },
      { id: '2', group: 'A' },
      { id: '3', group: 'B' },
    ];
    const out = groupLines(lines);
    expect(out.map((g) => g.group)).toEqual(['B', 'A']);
    expect(out[0].subjects.map((s) => s.id)).toEqual(['1', '3']);
  });
  it("uses '—' for lines with no group", () => {
    expect(groupLines([{ id: '1' }])[0].group).toBe('—');
  });
});

describe('serializePlotConfig', () => {
  const STATE = {
    xField: '__days__', groupBy: 'g1', tickStep: 2, metric: 'Success',
    hiddenSubjects: new Set(['b', 'a']), sidebarOpen: true,
    counts: { height: 220, yMin: '0', yMax: '', xZoomMin: '', xZoomMax: '', xFieldOverride: 'f2' },
    rate:   { height: 160, yMin: '', yMax: '', xZoomMin: '', xZoomMax: '', xFieldOverride: null },
  };

  it('produces a plain JSON object with hiddenSubjects as a sorted array', () => {
    const out = serializePlotConfig(STATE);
    expect(out.hiddenSubjects).toEqual(['a', 'b']);
    expect(out.sidebarOpen).toBe(true);
    expect(out.xField).toBe('__days__');
    expect(out.metric).toBe('Success');
    expect(out.counts).toEqual({ height: 220, yMin: '0', yMax: '', xZoomMin: '', xZoomMax: '', xFieldOverride: 'f2' });
    expect(out.rate.xFieldOverride).toBeNull();
  });

  it('round-trips through readPlotConfig (values preserved)', () => {
    const back = readPlotConfig(serializePlotConfig(STATE));
    expect(back.xField).toBe('__days__');
    expect(back.groupBy).toBe('g1');
    expect(back.tickStep).toBe(2);
    expect(back.hiddenSubjects).toEqual(['a', 'b']);
    expect(back.sidebarOpen).toBe(true);
    expect(back.counts.height).toBe(220);
    expect(back.counts.xFieldOverride).toBe('f2');
    expect(back.rate.height).toBe(160);
    expect(back.rate.xFieldOverride).toBeNull();
  });

  it('returns an empty hiddenSubjects array when state.hiddenSubjects is null', () => {
    expect(serializePlotConfig({ ...STATE, hiddenSubjects: null }).hiddenSubjects).toEqual([]);
  });
});

describe('readPlotConfig', () => {
  it('returns safe defaults for null', () => {
    const c = readPlotConfig(null);
    expect(c.hiddenSubjects).toEqual([]);
    expect(c.sidebarOpen).toBe(false);
    expect(c.xField).toBeUndefined();
    expect(c.counts.yMin).toBe('');
    expect(c.counts.xFieldOverride).toBeNull();
  });

  it('returns safe defaults for a non-object', () => {
    expect(readPlotConfig([1, 2]).hiddenSubjects).toEqual([]);
    expect(readPlotConfig('nope').sidebarOpen).toBe(false);
  });

  it('filters non-string hiddenSubjects and ignores wrong-typed fields', () => {
    const c = readPlotConfig({ hiddenSubjects: ['a', 5, 'b'], tickStep: 'x', sidebarOpen: 'yes' });
    expect(c.hiddenSubjects).toEqual(['a', 'b']);
    expect(c.tickStep).toBeUndefined();
    expect(c.sidebarOpen).toBe(false);
  });

  it('does not throw on malformed nested chart settings', () => {
    expect(() => readPlotConfig({ counts: 'bad', rate: [] })).not.toThrow();
    const c = readPlotConfig({ counts: 'bad' });
    expect(c.counts.yMin).toBe('');
    expect(c.counts.height).toBeUndefined();
  });
});
