import type { FdiToothId } from '@/types/treatment';
import { FDI_LOWER_LEFT_TO_RIGHT, FDI_UPPER_LEFT_TO_RIGHT } from '@/components/treatment/fdiToothMeta';

export type ToothSelectionKind = 'connected' | 'single';

export type ToothSelectionGroup = {
  groupId: string;
  kind: ToothSelectionKind;
  teeth: FdiToothId[];
};

export function newGroupId(): string {
  return typeof crypto !== 'undefined' && 'randomUUID' in crypto
    ? crypto.randomUUID()
    : `tg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}

export function deriveTeethFromGroups(groups: ToothSelectionGroup[]): FdiToothId[] {
  const set = new Set<FdiToothId>();
  for (const group of groups) {
    for (const tooth of group.teeth) set.add(tooth);
  }
  return [...set].sort() as FdiToothId[];
}

/** Never allow a 1-tooth connected group (no lone connection marks). */
function normalizeGroupKind(kind: ToothSelectionKind, teeth: FdiToothId[]): ToothSelectionKind {
  if (kind === 'connected' && teeth.length < 2) return 'single';
  return kind;
}

function makeGroup(kind: ToothSelectionKind, teeth: FdiToothId[]): ToothSelectionGroup | null {
  if (teeth.length === 0) return null;
  return {
    groupId: newGroupId(),
    kind: normalizeGroupKind(kind, teeth),
    teeth: sortInArchOrder(teeth),
  };
}

function sortInArchOrder(teeth: FdiToothId[]): FdiToothId[] {
  if (teeth.length === 0) return [];
  const arch = archOrder(teeth[0]);
  if (!arch) return [...teeth];
  return [...teeth].sort((a, b) => arch.indexOf(a) - arch.indexOf(b));
}

/** Legacy / empty groups: each selected tooth becomes its own single group. */
export function groupsFromFlatTeeth(
  teeth: readonly FdiToothId[],
  existing?: ToothSelectionGroup[] | null,
): ToothSelectionGroup[] {
  if (existing && existing.length > 0) {
    const flat = new Set(teeth);
    const next = existing
      .map((g) => ({
        ...g,
        teeth: g.teeth.filter((t) => flat.has(t)) as FdiToothId[],
      }))
      .filter((g) => g.teeth.length > 0)
      .map((g) => ({
        ...g,
        kind: normalizeGroupKind(g.kind, g.teeth),
        teeth: sortInArchOrder(g.teeth),
      }));
    const covered = new Set(next.flatMap((g) => g.teeth));
    for (const tooth of teeth) {
      if (!covered.has(tooth)) {
        next.push({ groupId: newGroupId(), kind: 'single', teeth: [tooth] });
      }
    }
    return next;
  }
  return teeth.map((tooth) => ({
    groupId: newGroupId(),
    kind: 'single' as const,
    teeth: [tooth],
  }));
}

export function connectedTeethSet(groups: ToothSelectionGroup[]): Set<FdiToothId> {
  const set = new Set<FdiToothId>();
  for (const group of groups) {
    if (group.kind !== 'connected') continue;
    for (const tooth of group.teeth) set.add(tooth);
  }
  return set;
}

/** Stable key for an adjacent pair (arch order). */
export function toothEdgeKey(a: FdiToothId, b: FdiToothId): string {
  const arch = archOrder(a);
  if (arch && sameArch(a, b)) {
    const i = arch.indexOf(a);
    const j = arch.indexOf(b);
    return i <= j ? `${a}-${b}` : `${b}-${a}`;
  }
  return a < b ? `${a}-${b}` : `${b}-${a}`;
}

/** Filled connection edges implied by connected groups (consecutive teeth in each bridge). */
export function linkedEdgesFromGroups(groups: ToothSelectionGroup[]): Set<string> {
  const edges = new Set<string>();
  for (const group of groups) {
    if (group.kind !== 'connected' || group.teeth.length < 2) continue;
    const ordered = sortInArchOrder(group.teeth);
    for (let i = 0; i < ordered.length - 1; i++) {
      const a = ordered[i];
      const b = ordered[i + 1];
      if (areArchNeighbors(a, b)) {
        edges.add(toothEdgeKey(a, b));
      }
    }
  }
  return edges;
}

export function areArchNeighbors(a: FdiToothId, b: FdiToothId): boolean {
  const arch = archOrder(a);
  if (!arch || !sameArch(a, b)) return false;
  return Math.abs(arch.indexOf(a) - arch.indexOf(b)) === 1;
}

function archOrder(tooth: FdiToothId): FdiToothId[] | null {
  if ((FDI_UPPER_LEFT_TO_RIGHT as readonly string[]).includes(tooth)) {
    return FDI_UPPER_LEFT_TO_RIGHT;
  }
  if ((FDI_LOWER_LEFT_TO_RIGHT as readonly string[]).includes(tooth)) {
    return FDI_LOWER_LEFT_TO_RIGHT;
  }
  return null;
}

export function sameArch(a: FdiToothId, b: FdiToothId): boolean {
  const archA = archOrder(a);
  const archB = archOrder(b);
  return Boolean(archA && archB && archA === archB);
}

export function teethBetweenInclusive(a: FdiToothId, b: FdiToothId): FdiToothId[] | null {
  const arch = archOrder(a);
  if (!arch || !sameArch(a, b)) return null;
  const i = arch.indexOf(a);
  const j = arch.indexOf(b);
  if (i < 0 || j < 0) return null;
  const [from, to] = i <= j ? [i, j] : [j, i];
  return arch.slice(from, to + 1);
}

/**
 * Plain click:
 * - unselected → add as single
 * - selected → deselect (remove). If it was in a bridge, split/shrink remaining sides.
 */
export function toggleToothInGroups(
  groups: ToothSelectionGroup[],
  tooth: FdiToothId,
): ToothSelectionGroup[] {
  const owning = groups.find((g) => g.teeth.includes(tooth));
  if (!owning) {
    return [...groups, { groupId: newGroupId(), kind: 'single', teeth: [tooth] }];
  }

  if (owning.kind === 'single') {
    return groups.filter((g) => g.groupId !== owning.groupId);
  }

  const ordered = sortInArchOrder(owning.teeth);
  const idx = ordered.indexOf(tooth);
  if (idx < 0) return groups;

  const left = ordered.slice(0, idx);
  const right = ordered.slice(idx + 1);
  const next = groups.filter((g) => g.groupId !== owning.groupId);

  const leftGroup = makeGroup(left.length >= 2 ? 'connected' : 'single', left);
  const rightGroup = makeGroup(right.length >= 2 ? 'connected' : 'single', right);
  if (leftGroup) next.push(leftGroup);
  if (rightGroup) next.push(rightGroup);
  return next;
}

/** Connect two adjacent selected teeth (merge into one connected bridge). */
export function linkAdjacentTeeth(
  groups: ToothSelectionGroup[],
  a: FdiToothId,
  b: FdiToothId,
): ToothSelectionGroup[] | null {
  if (!areArchNeighbors(a, b)) return null;
  const groupA = groups.find((g) => g.teeth.includes(a));
  const groupB = groups.find((g) => g.teeth.includes(b));
  if (!groupA || !groupB) return null;

  if (groupA.groupId === groupB.groupId) {
    if (groupA.kind === 'connected') return groups;
    return groups.map((g) =>
      g.groupId === groupA.groupId
        ? { ...g, kind: 'connected' as const, teeth: sortInArchOrder(g.teeth) }
        : g,
    );
  }

  const mergedTeeth = sortInArchOrder([...groupA.teeth, ...groupB.teeth]);
  const next = groups.filter((g) => g.groupId !== groupA.groupId && g.groupId !== groupB.groupId);
  const merged = makeGroup('connected', mergedTeeth);
  if (merged) next.push(merged);
  return next;
}

/** Disconnect one edge inside a bridge; teeth stay selected. */
export function unlinkAdjacentTeeth(
  groups: ToothSelectionGroup[],
  a: FdiToothId,
  b: FdiToothId,
): ToothSelectionGroup[] | null {
  if (!areArchNeighbors(a, b)) return null;
  const owning = groups.find(
    (g) => g.kind === 'connected' && g.teeth.includes(a) && g.teeth.includes(b),
  );
  if (!owning) return null;

  const ordered = sortInArchOrder(owning.teeth);
  const i = ordered.indexOf(a);
  const j = ordered.indexOf(b);
  if (i < 0 || j < 0 || Math.abs(i - j) !== 1) return null;

  const [leftIdx, rightIdx] = i < j ? [i, j] : [j, i];
  const left = ordered.slice(0, rightIdx);
  const right = ordered.slice(rightIdx);
  const next = groups.filter((g) => g.groupId !== owning.groupId);
  const leftGroup = makeGroup(left.length >= 2 ? 'connected' : 'single', left);
  const rightGroup = makeGroup(right.length >= 2 ? 'connected' : 'single', right);
  if (leftGroup) next.push(leftGroup);
  if (rightGroup) next.push(rightGroup);
  return next;
}

export function shiftRangeTeeth(
  groups: ToothSelectionGroup[],
  anchor: FdiToothId,
  target: FdiToothId,
): FdiToothId[] | null {
  if (anchor === target) return null;
  const span = teethBetweenInclusive(anchor, target);
  if (!span || span.length < 2) return null;

  const union = new Set<FdiToothId>(span);
  let changed = true;
  while (changed) {
    changed = false;
    for (const group of groups) {
      if (group.kind !== 'connected') continue;
      if (!group.teeth.some((t) => union.has(t))) continue;
      for (const t of group.teeth) {
        if (!union.has(t)) {
          union.add(t);
          changed = true;
        }
      }
    }
  }

  const unionTeeth = sortInArchOrder([...union] as FdiToothId[]);
  return unionTeeth.length < 2 ? null : unionTeeth;
}

/**
 * Shift-click selects an inclusive same-arch span as singles (empty circles between
 * neighbors — not connected). Overlapping existing bridges are absorbed into that
 * selection and become singles too (one selected range, not connected).
 * Midline pairs (11–21, 41–31) are allowed — they are neighbors in arch order.
 */
export function applyShiftRange(
  groups: ToothSelectionGroup[],
  anchor: FdiToothId,
  target: FdiToothId,
): ToothSelectionGroup[] | null {
  const unionTeeth = shiftRangeTeeth(groups, anchor, target);
  if (!unionTeeth) return null;
  const union = new Set(unionTeeth);

  const next = groups
    .map((g) => ({
      ...g,
      teeth: g.teeth.filter((t) => !union.has(t)) as FdiToothId[],
    }))
    .filter((g) => g.teeth.length > 0)
    .map((g) => ({
      ...g,
      kind: normalizeGroupKind(g.kind, g.teeth),
    }));

  for (const tooth of unionTeeth) {
    next.push({ groupId: newGroupId(), kind: 'single', teeth: [tooth] });
  }
  return next;
}

/**
 * Drop prosthesis rows for teeth no longer selected on a detail, and refresh
 * selectionGroupId so group changes do not leave stale group ids.
 */
export function pruneToothProsthesisForGroups<
  T extends { detailClientId: string; tooth: string; selectionGroupId?: string },
>(toothProsthesis: T[], detailClientId: string, groups: ToothSelectionGroup[]): T[] {
  const toothToGroup = new Map<string, ToothSelectionGroup>();
  for (const group of groups) {
    for (const tooth of group.teeth) toothToGroup.set(tooth, group);
  }
  return toothProsthesis
    .filter(
      (tp) =>
        tp.detailClientId !== detailClientId ||
        toothToGroup.has(tp.tooth) ||
        tp.tooth === 'UA' ||
        tp.tooth === 'LA',
    )
    .map((tp) => {
      if (tp.detailClientId !== detailClientId) return tp;
      const group = toothToGroup.get(tp.tooth);
      if (!group) return tp;
      return { ...tp, selectionGroupId: group.groupId };
    });
}

export function normalizeToothSelectionGroups(value: unknown): ToothSelectionGroup[] {
  if (!Array.isArray(value)) return [];
  const out: ToothSelectionGroup[] = [];
  for (const row of value) {
    if (!row || typeof row !== 'object') continue;
    const rec = row as Record<string, unknown>;
    const groupId = typeof rec.groupId === 'string' ? rec.groupId : newGroupId();
    const kind = rec.kind === 'connected' ? 'connected' : 'single';
    const teeth = Array.isArray(rec.teeth)
      ? (rec.teeth.filter((t) => typeof t === 'string') as FdiToothId[])
      : [];
    if (teeth.length === 0) continue;
    out.push({
      groupId,
      kind: normalizeGroupKind(kind, teeth),
      teeth: sortInArchOrder(teeth),
    });
  }
  return out;
}
