/**
 * Pure scope-to-effort expansion engine.
 * Takes scope items and effort rules, produces demand line drafts.
 * No DB or IO dependencies.
 */

export type EffortUnitMode = "per_frame" | "per_item" | "flat";

export interface EffortRuleInput {
  scopeType: string;
  discipline: string;
  chapter?: string | null;
  unitMode: EffortUnitMode;
  hoursPerUnit: number;
  sortOrder: number;
}

export interface ScopeItemInput {
  name: string;
  scopeType: string;
  frameCount?: number | null;
  itemCount?: number | null;
  unitMode?: string | null;
}

export interface ExpandedDemandLine {
  scopeItemName: string;
  scopeType: string;
  discipline: string;
  chapter?: string | null;
  hours: number;
  unitMode: EffortUnitMode;
  unitCount: number;
  hoursPerUnit: number;
}

export interface EffortExpansionResult {
  lines: ExpandedDemandLine[];
  warnings: string[];
  unmatchedScopeItems: string[];
}

function getUnitCount(
  item: ScopeItemInput,
  unitMode: EffortUnitMode,
): number {
  switch (unitMode) {
    case "per_frame":
      return item.frameCount ?? 1;
    case "per_item":
      return item.itemCount ?? 1;
    case "flat":
      return 1;
  }
}

/**
 * Expand scope items into demand line drafts using effort rules.
 *
 * For each scope item, finds all matching rules by scopeType and generates
 * one demand line per matching rule. Hours = unitCount * hoursPerUnit.
 *
 * Rules are matched case-insensitively on scopeType.
 */
export function expandScopeToEffort(
  scopeItems: ScopeItemInput[],
  rules: EffortRuleInput[],
): EffortExpansionResult {
  const lines: ExpandedDemandLine[] = [];
  const warnings: string[] = [];
  const unmatchedScopeItems: string[] = [];

  if (rules.length === 0) {
    warnings.push("No effort rules provided.");
    return { lines, warnings, unmatchedScopeItems };
  }

  // Index rules by normalized scopeType for fast lookup
  const rulesByScopeType = new Map<string, EffortRuleInput[]>();
  for (const rule of rules) {
    const key = rule.scopeType.toLowerCase().trim();
    const existing = rulesByScopeType.get(key) ?? [];
    existing.push(rule);
    rulesByScopeType.set(key, existing);
  }

  for (const item of scopeItems) {
    if (!item.name.trim()) continue;

    const normalizedType = item.scopeType.toLowerCase().trim();
    const matchingRules = rulesByScopeType.get(normalizedType);

    if (!matchingRules || matchingRules.length === 0) {
      unmatchedScopeItems.push(item.name);
      continue;
    }

    // Sort by sortOrder for deterministic output
    const sortedRules = [...matchingRules].sort((a, b) => a.sortOrder - b.sortOrder);

    for (const rule of sortedRules) {
      const unitCount = getUnitCount(item, rule.unitMode);
      const hours = Math.round(unitCount * rule.hoursPerUnit * 100) / 100;

      if (hours <= 0) {
        warnings.push(
          `Skipped "${rule.discipline}" for "${item.name}": computed hours is 0 (unitCount=${unitCount}, hoursPerUnit=${rule.hoursPerUnit}).`,
        );
        continue;
      }

      lines.push({
        scopeItemName: item.name,
        scopeType: item.scopeType,
        discipline: rule.discipline,
        ...(rule.chapter != null ? { chapter: rule.chapter } : {}),
        hours,
        unitMode: rule.unitMode,
        unitCount,
        hoursPerUnit: rule.hoursPerUnit,
      });
    }
  }

  if (unmatchedScopeItems.length > 0) {
    warnings.push(
      `${unmatchedScopeItems.length} scope item(s) had no matching rules: ${unmatchedScopeItems.slice(0, 5).join(", ")}${unmatchedScopeItems.length > 5 ? "..." : ""}.`,
    );
  }

  return { lines, warnings, unmatchedScopeItems };
}

/**
 * Aggregate expanded lines by discipline, summing hours.
 * Useful for creating one demand line per discipline instead of per scope item.
 */
export function aggregateByDiscipline(
  lines: ExpandedDemandLine[],
): Array<{ discipline: string; chapter?: string | null; totalHours: number; lineCount: number }> {
  const map = new Map<string, { discipline: string; chapter?: string | null; totalHours: number; lineCount: number }>();

  for (const line of lines) {
    const key = `${line.discipline}::${line.chapter ?? ""}`;
    const existing = map.get(key);
    if (existing) {
      existing.totalHours += line.hours;
      existing.lineCount++;
    } else {
      map.set(key, {
        discipline: line.discipline,
        ...(line.chapter != null ? { chapter: line.chapter } : {}),
        totalHours: line.hours,
        lineCount: 1,
      });
    }
  }

  return [...map.values()].sort((a, b) => b.totalHours - a.totalHours);
}
