// 2026-11-11 — Duplicate companies, flagged in the Companies list and in Create Job.
//
// Same matching rule as the Merge Duplicates page (CompanyMergeService.findDuplicateGroups):
// CompanyResolverService.normalize(name), then letters/digits only, keys shorter than 4 ignored.
// Built from all ~8,500 names in ~0.3 s and cached for 2 minutes (shared by every request).
import { DataSource } from 'typeorm';
import { CompanyResolverService } from './company-resolver.service';

// normalize() is pure (it does not use the repositories), so a bare instance is enough here.
const resolver = new CompanyResolverService(null as any, null as any);

export interface DuplicateIndex {
  keyOf: Map<number, string>; // company id → match key (only companies that HAVE duplicates)
  members: Map<string, number[]>; // key → company ids (2+)
  at: number;
}

let cache: DuplicateIndex | null = null;
let building: Promise<DuplicateIndex> | null = null;

export function dupKey(name: string): string {
  return resolver.normalize(name || '').replace(/[^a-z0-9]/g, '');
}

// Second, looser key: the full name letters/digits only, with legal-form words cut from the END
// (catches "TRUEFORGE FZLLC" = "TRUEFORGE FZ-LLC", which normalize() splits at the "-").
const ENTITY_TAILS = [
  'soleproprietorshipllc', 'soleproprietorship', 'llcspc', 'llcsoc', 'llcopc', 'fzllc', 'fzco', 'fze', 'fz', 'llc',
  'spc', 'soc', 'opc', 'wll', 'plc', 'ltd', 'limited', 'inc', 'est', 'establishment', 'company', 'co', 'lc', 'so',
];
export function looseKey(name: string): string {
  let s = String(name || '').toLowerCase().replace(/[^a-z0-9]/g, '');
  for (let changed = true; changed; ) {
    changed = false;
    for (const t of ENTITY_TAILS) {
      if (s.length > t.length + 4 && s.endsWith(t)) {
        s = s.slice(0, -t.length);
        changed = true;
      }
    }
  }
  return s;
}

export async function getDuplicateIndex(db: DataSource, maxAgeMs = 120_000): Promise<DuplicateIndex> {
  if (cache && Date.now() - cache.at < maxAgeMs) return cache;
  if (building) return building;
  building = (async () => {
    const all: { id: number; name: string }[] = await db.query(`SELECT id, name FROM companies`);
    // union-find over both keys: same normalize() key (as the Merge page) OR same loose key
    const parent = new Map<number, number>();
    const find = (x: number): number => {
      let r = x;
      while (parent.get(r) !== r) r = parent.get(r)!;
      while (parent.get(x) !== r) {
        const n = parent.get(x)!;
        parent.set(x, r);
        x = n;
      }
      return r;
    };
    const union = (a: number, b: number) => {
      const ra = find(a);
      const rb = find(b);
      if (ra !== rb) parent.set(Math.max(ra, rb), Math.min(ra, rb));
    };
    const first = new Map<string, number>();
    for (const c of all) {
      const id = Number(c.id);
      parent.set(id, id);
      const k1 = dupKey(c.name);
      const k2 = looseKey(c.name);
      for (const k of [k1.length >= 4 ? `n:${k1}` : '', k2.length >= 5 ? `l:${k2}` : '']) {
        if (!k) continue;
        const f = first.get(k);
        if (f === undefined) first.set(k, id);
        else union(f, id);
      }
    }
    const buckets = new Map<string, number[]>();
    for (const c of all) {
      const root = String(find(Number(c.id)));
      const list = buckets.get(root) ?? [];
      list.push(Number(c.id));
      buckets.set(root, list);
    }
    const keyOf = new Map<number, string>();
    const members = new Map<string, number[]>();
    for (const [k, ids] of buckets) {
      if (ids.length < 2) continue;
      members.set(k, ids.sort((a, b) => a - b));
      for (const id of ids) keyOf.set(id, k);
    }
    cache = { keyOf, members, at: Date.now() };
    return cache;
  })().finally(() => {
    building = null;
  });
  return building;
}

/** 2026-11-11 — drop the cached groups (called after a merge so the ⚠ DUPLICATE tags update at once) */
export function resetDuplicateIndex(): void {
  cache = null;
}

/** ids of the OTHER companies that look like duplicates of this one */
export async function duplicatesOf(db: DataSource, companyId: number): Promise<number[]> {
  const idx = await getDuplicateIndex(db);
  const k = idx.keyOf.get(Number(companyId));
  return k ? (idx.members.get(k) ?? []).filter((x) => x !== Number(companyId)) : [];
}

/** all company ids that belong to a duplicate group */
export async function allDuplicateIds(db: DataSource): Promise<number[]> {
  return [...(await getDuplicateIndex(db)).keyOf.keys()];
}
