import type {
  AgentDecomposeResult,
  AgentDevTaskDraft,
  AgentReference,
  AgentTestCaseDraft,
} from '@ftb/shared';
import type { DevTask, Reference } from './dev-task';
import type { TestCase } from './test-case';
import type { TaskCategory } from './task-category';

interface RequirementIdentity {
  id: string;
  code: string;
}

export interface DecomposeDedupeInput {
  existingDevTasks: DevTask[];
  existingTestCases: TestCase[];
  categories: TaskCategory[];
  requirements: RequirementIdentity[];
}

export interface DecomposeDedupeResult {
  result: AgentDecomposeResult;
  removedDevTaskCount: number;
  removedTestCaseCount: number;
}

export function filterDuplicateDecomposeDrafts(
  result: AgentDecomposeResult,
  input: DecomposeDedupeInput,
): DecomposeDedupeResult {
  const devFingerprints = new Set(
    input.existingDevTasks
      .map((task) => existingItemFingerprint(task.title, task.categoryId, task.references ?? [], input))
      .filter(Boolean),
  );
  const testCaseFingerprints = new Set(
    input.existingTestCases
      .map((testCase) => existingItemFingerprint(testCase.title, testCase.categoryId, testCase.references ?? [], input))
      .filter(Boolean),
  );

  const filteredDevTaskDrafts = filterDrafts(result.devTaskDrafts, devFingerprints, input, draftFingerprint);
  const filteredTestCaseDrafts = filterDrafts(result.testCaseDrafts, testCaseFingerprints, input, draftFingerprint);

  return {
    result: {
      ...result,
      devTaskDrafts: filteredDevTaskDrafts.items,
      testCaseDrafts: filteredTestCaseDrafts.items,
    },
    removedDevTaskCount: result.devTaskDrafts.length - filteredDevTaskDrafts.items.length,
    removedTestCaseCount: result.testCaseDrafts.length - filteredTestCaseDrafts.items.length,
  };
}

function filterDrafts<T extends AgentDevTaskDraft | AgentTestCaseDraft>(
  drafts: T[],
  existingFingerprints: Set<string>,
  input: DecomposeDedupeInput,
  getFingerprint: (draft: T, input: DecomposeDedupeInput) => string,
): { items: T[] } {
  const seen = new Set<string>();
  const items: T[] = [];
  for (const draft of drafts) {
    const fingerprint = getFingerprint(draft, input);
    if (existingFingerprints.has(fingerprint) || seen.has(fingerprint)) continue;
    seen.add(fingerprint);
    items.push(draft);
  }
  return { items };
}

function draftFingerprint(
  draft: AgentDevTaskDraft | AgentTestCaseDraft,
  input: DecomposeDedupeInput,
): string {
  return buildFingerprint(draft.title, draftCategoryIdentity(draft, input), draft.references, input);
}

function existingItemFingerprint(
  title: string,
  categoryId: string,
  references: Reference[],
  input: DecomposeDedupeInput,
): string {
  const categoryName = input.categories.find((category) => category.id === categoryId)?.name ?? categoryId;
  return buildFingerprint(title, categoryName, references, input);
}

function draftCategoryIdentity(
  draft: AgentDevTaskDraft | AgentTestCaseDraft,
  input: DecomposeDedupeInput,
): string {
  const explicitName = draft.taskTypeName?.trim();
  if (explicitName) return explicitName;
  if (draft.categoryCode) {
    return input.categories.find((category) => category.code === draft.categoryCode)?.name ?? draft.categoryCode;
  }
  return '';
}

function buildFingerprint(
  title: string,
  categoryName: string,
  references: Array<AgentReference | Reference>,
  input: DecomposeDedupeInput,
): string {
  const refKey = references
    .map((ref) => normalizeReferenceKey(ref, input.requirements))
    .filter(Boolean)
    .sort()
    .join('|');
  return `${normalizeTitle(categoryName)}::${normalizeTitle(title)}::${refKey}`;
}

function normalizeTitle(title: string): string {
  return title
    .trim()
    .toLowerCase()
    .replace(/[\s:：,，.。;；、\-—_]/g, '');
}

function normalizeReferenceKey(
  ref: AgentReference | Reference,
  requirements: RequirementIdentity[],
): string {
  const rawId = String(ref.id || '').trim();
  if (!rawId) return '';
  if (ref.type === 'requirement') {
    const matched = requirements.find((requirement) => requirement.id === rawId || requirement.code === rawId);
    return `requirement:${matched?.id ?? rawId}`;
  }
  return `${ref.type}:${rawId.toLowerCase()}`;
}
