import type { WeekdayAvailability } from "@capakraken/shared";
import { fmtEur } from "../lib/format-utils.js";
import { type GraphLink, type GraphNode, fmtPct, fmtNum, l, n } from "./computation-graph-shared.js";

type ResourceGraphResolvedHoliday = {
  date: string;
  name: string;
  scope: "COUNTRY" | "STATE" | "CITY";
  calendarName: string | null;
  sourceType: string | null;
};

type ResourceAssignmentBreakdown = {
  id: string;
  projectId: string;
  projectName: string;
  projectCode: string;
  status: string;
  bookedHours: number;
};

type ResourceGraphInputResource = {
  displayName: string;
  eid: string;
  fte: number;
  lcrCents: number;
  country: {
    code: string;
    name: string;
  } | null;
  federalState: string | null;
  metroCity: {
    name: string;
  } | null;
  managementLevelGroup: {
    name: string;
  } | null;
};

type ResourceForecastSummary = {
  chg: number;
  bd: number;
  mdi: number;
  mo: number;
  pdr: number;
  absence: number;
  unassigned: number;
};

type ResourceGraphPresentationInput = {
  month: string;
  resource: ResourceGraphInputResource;
  dailyHours: number;
  scheduleRules: unknown | null;
  targetPct: number;
  weeklyAvailability: WeekdayAvailability;
  holidayScopeSummary: string;
  holidayExamples: string;
  holidayScopeBreakdown: Record<string, number>;
  calcRulesCount: number;
  assignmentCount: number;
  absenceCount: number;
  vacationDayCount: number;
  sickDayCount: number;
  halfDayCount: number;
  publicHolidayCount: number;
  publicHolidayWorkdayCount: number;
  publicHolidayHoursDeduction: number;
  absenceHoursDeduction: number;
  sahCalendarDays: number;
  sahWeekendDays: number;
  baseWorkingDays: number;
  effectiveWorkingDays: number;
  baseAvailableHours: number;
  effectiveAvailableHours: number;
  effectiveHoursPerWorkingDay: number;
  totalWorkingDaysInMonth: number;
  totalAllocHours: number;
  totalAllocCostCents: number;
  totalChargeableHours: number;
  totalProjectCostCents: number;
  hasRulesEffect: boolean;
  dailyCostCents: number;
  avgHoursPerDay: number;
  utilizationPct: number;
  forecast: ResourceForecastSummary;
  chargeableHours: number;
  budgetNodes: GraphNode[];
  budgetLinks: GraphLink[];
  resolvedHolidays: ResourceGraphResolvedHoliday[];
  assignmentBreakdown: ResourceAssignmentBreakdown[];
  absenceDayEquivalent: number;
};

function describeWeeklyAvailability(availability: WeekdayAvailability): {
  totalHours: number;
  label: string;
} {
  const weekdayLabels = ["Mo", "Tu", "We", "Th", "Fr"];
  const weekdayValues = [
    availability.monday,
    availability.tuesday,
    availability.wednesday,
    availability.thursday,
    availability.friday,
  ];
  const totalHours = weekdayValues.reduce((sum, value) => sum + value, 0);
  const allSame = weekdayValues.every((value) => value === weekdayValues[0]);

  return {
    totalHours,
    label: allSame
      ? `${weekdayValues[0]}h/day`
      : weekdayLabels.map((day, index) => `${day}:${weekdayValues[index]}`).join(" "),
  };
}

export function buildResourceGraphSnapshot(input: ResourceGraphPresentationInput) {
  const { totalHours: weeklyTotalHours, label: availabilityLabel } = describeWeeklyAvailability(input.weeklyAvailability);
  const hasScheduleRules = !!input.scheduleRules;

  const nodes: GraphNode[] = [
    n("input.fte", "FTE", fmtNum(input.resource.fte, 2), "ratio", "INPUT", "Resource FTE factor", 0),
    n("input.country", "Country", input.resource.country?.name ?? input.resource.country?.code ?? "—", "text", "INPUT", "Country used for base working-time and national holiday rules", 0),
    n("input.state", "State", input.resource.federalState ?? "—", "text", "INPUT", "Federal state / region used for regional holidays", 0),
    n("input.city", "City", input.resource.metroCity?.name ?? "—", "text", "INPUT", "City / metro used for local holidays", 0),
    n("input.holidayContext", "Holiday Context", input.holidayScopeSummary, "text", "INPUT", "Resolved holiday scope chain: country / state / city", 0),
    n("input.holidayExamples", "Holiday Dates", input.holidayExamples, "text", "INPUT", `Resolved holidays in ${input.month}; scopes: COUNTRY ${input.holidayScopeBreakdown.COUNTRY ?? 0}, STATE ${input.holidayScopeBreakdown.STATE ?? 0}, CITY ${input.holidayScopeBreakdown.CITY ?? 0}`, 0),
    n("input.dailyHours", "Country Hours", `${input.dailyHours} h`, "hours", "INPUT", `Base daily working hours (${input.resource.country?.code ?? "?"})`, 0),
    ...(hasScheduleRules
      ? [n("input.scheduleRules", "Schedule Rules", "Spain", "—", "INPUT", "Variable daily hours (regular/friday/summer)", 0)]
      : []),
    n("input.weeklyAvail", "Weekly Avail.", `${weeklyTotalHours}h`, "h/week", "INPUT", `Resource availability: ${availabilityLabel}`, 0),
    n("input.lcrCents", "LCR", fmtEur(input.resource.lcrCents), "cents/h", "INPUT", "Loaded Cost Rate per hour", 0),
    n("input.hoursPerDay", "Hours/Day", fmtNum(input.avgHoursPerDay), "hours", "INPUT", "Average hours/day across assignments", 0),
    n("input.absences", "Absences", `${input.absenceCount}`, "count", "INPUT", `Absence days in ${input.month} (${input.vacationDayCount} vacation, ${input.sickDayCount} sick${input.halfDayCount > 0 ? `, ${input.halfDayCount} half-day` : ""})`, 0),
    n("input.publicHolidays", "Public Holidays", `${input.publicHolidayCount}`, "count", "INPUT", `Resolved holidays in ${input.month}; ${input.publicHolidayWorkdayCount} hit configured working days`, 0),
    n("input.calcRules", "Active Rules", `${input.calcRulesCount}`, "count", "INPUT", "Active calculation rules", 0),
    n("input.targetPct", "Target", fmtPct(input.targetPct), "%", "INPUT", `Chargeability target (${input.resource.managementLevelGroup?.name ?? "legacy"})`, 0),
    n("input.assignmentCount", "Assignments", `${input.assignmentCount}`, "count", "INPUT", `Active assignments in ${input.month}`, 0),

    n("sah.calendarDays", "Calendar Days", `${input.sahCalendarDays}`, "days", "SAH", "Total calendar days in period", 1),
    n("sah.weekendDays", "Weekend Days", `${input.sahWeekendDays}`, "days", "SAH", "Saturday + Sunday count", 1),
    n("sah.grossWorkingDays", "Gross Work Days", `${input.baseWorkingDays}`, "days", "SAH", "Working days from the resource-specific weekly availability before holidays/absences", 1, "count(availability > 0)"),
    n("sah.baseHours", "Base Hours", fmtNum(input.baseAvailableHours), "hours", "SAH", "Available hours from weekly availability before holiday/absence deductions", 1, "Σ(daily availability)"),
    n("sah.publicHolidayDays", "Holiday Ded.", `${input.publicHolidayWorkdayCount}`, "days", "SAH", "Holiday workdays deducted after applying country/state/city scope and weekday availability", 1),
    n("sah.publicHolidayHours", "Holiday Hrs Ded.", fmtNum(input.publicHolidayHoursDeduction), "hours", "SAH", "Hours removed by resolved public holidays", 1, "Σ(availability on holiday dates)"),
    n("sah.absenceDays", "Absence Ded.", `${input.absenceCount}`, "days", "SAH", "Vacation/sick days that hit working days and are not already public holidays", 1),
    n("sah.absenceHours", "Absence Hrs Ded.", fmtNum(input.absenceHoursDeduction), "hours", "SAH", "Hours removed by vacation/sick absences", 1, "Σ(availability × absence fraction)"),
    n("sah.netWorkingDays", "Net Work Days", `${input.effectiveWorkingDays}`, "days", "SAH", "Remaining working days after holiday and absence deductions", 2, "gross - holidays - absences"),
    n("sah.effectiveHoursPerDay", "Eff. Hrs/Day", fmtNum(input.effectiveHoursPerWorkingDay), "hours", "SAH", "Average effective hours per remaining working day", 2, "SAH / net work days"),
    n("sah.sah", "SAH", fmtNum(input.effectiveAvailableHours), "hours", "SAH", "Effective available hours after weekly availability, local holidays and absences", 2, "base hours - holiday hours - absence hours"),

    n("alloc.workingDays", "Work Days", `${input.totalWorkingDaysInMonth}`, "days", "ALLOCATION", "Working days covered by assignments in period", 1, "Σ(overlap workdays)"),
    n("alloc.totalHours", "Total Hours", fmtNum(input.totalAllocHours), "hours", "ALLOCATION", "Sum of effective hours across assignments", 2, "Σ(min(h/day, avail) × workdays)"),
    n("alloc.dailyCostCents", "Daily Cost", fmtEur(input.dailyCostCents), "EUR", "ALLOCATION", "Cost per working day", 1, "hoursPerDay × LCR"),
    n("alloc.totalCostCents", "Total Cost", fmtEur(input.totalAllocCostCents), "EUR", "ALLOCATION", "Sum of daily costs", 2, "Σ(dailyCost × workdays)"),
    n("alloc.utilizationPct", "Utilization", `${input.utilizationPct.toFixed(1)}%`, "%", "ALLOCATION", "Allocation utilization: allocated hours / SAH", 3, "totalHours / SAH × 100"),
    ...(input.hasRulesEffect
      ? [
        n("alloc.chargeableHours", "Chargeable Hrs", fmtNum(input.totalChargeableHours), "hours", "ALLOCATION", "Rules-adjusted chargeable hours", 2, "rules-adjusted"),
        n("alloc.projectCostCents", "Project Cost", fmtEur(input.totalProjectCostCents), "EUR", "ALLOCATION", "Rules-adjusted project cost", 2, "rules-adjusted"),
      ]
      : []),

    ...(input.absenceCount > 0
      ? [
        n("rules.activeRules", "Matched Rules", `${input.calcRulesCount} rules`, "—", "RULES", "Rules evaluated for absence days", 1),
        n("rules.costEffect", "Cost Effect", input.hasRulesEffect ? "ZERO" : "—", "—", "RULES", "How absent days affect project cost", 1, "CHARGE / ZERO / REDUCE"),
        n("rules.chgEffect", "Chg Effect", input.hasRulesEffect ? "COUNT" : "—", "—", "RULES", "How absent days affect chargeability", 1, "COUNT / SKIP"),
        ...(input.hasRulesEffect
          ? [n("rules.costReduction", "Cost Reduction", "per rule", "—", "RULES", "Cost reduction percentage applied to absent hours", 2, "normalCost × (100 - reductionPct) / 100")]
          : []),
      ]
      : []),

    n("chg.chgHours", "Chg Hours", fmtNum(input.chargeableHours), "hours", "CHARGEABILITY", "Total chargeable hours against effective SAH", 2, "chargeability × SAH"),
    n("chg.chg", "Chargeability", fmtPct(input.forecast.chg), "%", "CHARGEABILITY", "Chargeability ratio", 3, "chgHours / SAH"),
    ...(input.forecast.bd > 0
      ? [n("chg.bd", "BD Ratio", fmtPct(input.forecast.bd), "%", "CHARGEABILITY", `Business development: ${fmtNum(input.forecast.bd * input.effectiveAvailableHours)}h`, 3, "bdHours / SAH")]
      : []),
    ...(input.forecast.mdi > 0
      ? [n("chg.mdi", "MD&I Ratio", fmtPct(input.forecast.mdi), "%", "CHARGEABILITY", `MD&I hours: ${fmtNum(input.forecast.mdi * input.effectiveAvailableHours)}h`, 3, "mdiHours / SAH")]
      : []),
    ...(input.forecast.mo > 0
      ? [n("chg.mo", "M&O Ratio", fmtPct(input.forecast.mo), "%", "CHARGEABILITY", `M&O hours: ${fmtNum(input.forecast.mo * input.effectiveAvailableHours)}h`, 3, "moHours / SAH")]
      : []),
    ...(input.forecast.pdr > 0
      ? [n("chg.pdr", "PD&R Ratio", fmtPct(input.forecast.pdr), "%", "CHARGEABILITY", `PD&R hours: ${fmtNum(input.forecast.pdr * input.effectiveAvailableHours)}h`, 3, "pdrHours / SAH")]
      : []),
    ...(input.forecast.absence > 0
      ? [n("chg.absence", "Absence Ratio", fmtPct(input.forecast.absence), "%", "CHARGEABILITY", `Absence hours: ${fmtNum(input.forecast.absence * input.effectiveAvailableHours)}h`, 3, "absenceHours / SAH")]
      : []),
    n("chg.unassigned", "Unassigned", fmtPct(input.forecast.unassigned), "%", "CHARGEABILITY", `${fmtNum(input.forecast.unassigned * input.effectiveAvailableHours)}h of ${fmtNum(input.effectiveAvailableHours)}h SAH not assigned`, 3, "max(0, SAH - assigned) / SAH"),
    n("chg.target", "Target", fmtPct(input.targetPct), "%", "CHARGEABILITY", "Chargeability target from management level", 3),
    n("chg.gap", "Gap to Target", `${input.forecast.chg - input.targetPct >= 0 ? "+" : ""}${((input.forecast.chg - input.targetPct) * 100).toFixed(1)} pp`, "pp", "CHARGEABILITY", `Chargeability (${fmtPct(input.forecast.chg)}) vs. target (${fmtPct(input.targetPct)})`, 3, "chargeability − target"),

    ...input.budgetNodes,
  ];

  const links: GraphLink[] = [
    l("input.country", "input.holidayContext", "holiday base", 1),
    l("input.state", "input.holidayContext", "regional scope", 1),
    l("input.city", "input.holidayContext", "local scope", 1),
    l("input.holidayContext", "input.holidayExamples", "resolve holidays", 1),
    l("input.dailyHours", "sah.grossWorkingDays", "base hours", 1),
    l("input.weeklyAvail", "sah.grossWorkingDays", "working-day pattern", 2),
    l("input.weeklyAvail", "sah.baseHours", "sum by weekday", 2),
    l("input.holidayExamples", "sah.publicHolidayDays", "resolved dates", 2),
    l("input.holidayExamples", "sah.publicHolidayHours", "remove matching day hours", 2),
    l("input.absences", "sah.absenceHours", "remove absence fractions", 1),
    ...(hasScheduleRules ? [l("input.scheduleRules", "sah.effectiveHoursPerDay", "variable h/day", 1)] : []),
    l("sah.calendarDays", "sah.grossWorkingDays", "− weekends", 2),
    l("sah.weekendDays", "sah.grossWorkingDays", "−", 1),
    l("input.publicHolidays", "sah.publicHolidayDays", "∩ workdays", 1),
    l("input.absences", "sah.absenceDays", "∩ workdays", 1),
    l("sah.grossWorkingDays", "sah.netWorkingDays", "− holiday/absence days", 2),
    l("sah.publicHolidayDays", "sah.netWorkingDays", "−", 1),
    l("sah.absenceDays", "sah.netWorkingDays", "−", 1),
    l("sah.baseHours", "sah.sah", "start from base capacity", 2),
    l("sah.publicHolidayHours", "sah.sah", "− holiday hours", 2),
    l("sah.absenceHours", "sah.sah", "− absence hours", 2),
    l("sah.sah", "sah.effectiveHoursPerDay", "÷", 1),
    l("sah.netWorkingDays", "sah.effectiveHoursPerDay", "÷", 1),

    l("input.weeklyAvail", "alloc.totalHours", "caps h/day", 2),
    l("input.hoursPerDay", "alloc.dailyCostCents", "×", 1),
    l("input.lcrCents", "alloc.dailyCostCents", "× LCR", 2),
    l("input.hoursPerDay", "alloc.workingDays", "per assignment", 1),
    l("input.assignmentCount", "alloc.workingDays", "× overlap", 1),
    l("alloc.workingDays", "alloc.totalHours", "× h/day", 2),
    l("input.hoursPerDay", "alloc.totalHours", "× workdays", 1),
    l("alloc.dailyCostCents", "alloc.totalCostCents", "× workdays", 2),
    l("alloc.workingDays", "alloc.totalCostCents", "×", 1),
    l("alloc.totalHours", "alloc.utilizationPct", "÷ SAH × 100", 2),
    l("sah.sah", "alloc.utilizationPct", "÷", 1),

    ...(input.absenceCount > 0
      ? [
        l("input.calcRules", "rules.activeRules", "filter active", 1),
        l("input.absences", "rules.activeRules", "match trigger", 1),
        l("rules.activeRules", "rules.costEffect", "→ effect", 1),
        l("rules.activeRules", "rules.chgEffect", "→ effect", 1),
      ]
      : []),

    ...(input.hasRulesEffect
      ? [
        l("rules.costEffect", "alloc.projectCostCents", "apply", 2),
        l("alloc.totalCostCents", "alloc.projectCostCents", "adjust", 1),
        l("rules.chgEffect", "alloc.chargeableHours", "apply", 2),
        l("alloc.totalHours", "alloc.chargeableHours", "adjust", 1),
        ...(input.absenceCount > 0 ? [l("rules.costEffect", "rules.costReduction", "reduce %", 1)] : []),
      ]
      : []),

    l(input.hasRulesEffect ? "alloc.chargeableHours" : "alloc.totalHours", "chg.chgHours", "Σ Chg", 2),
    l("chg.chgHours", "chg.chg", "÷ SAH", 2),
    l("sah.sah", "chg.chg", "÷", 2),
    ...(input.forecast.bd > 0 ? [l("sah.sah", "chg.bd", "÷", 1)] : []),
    ...(input.forecast.mdi > 0 ? [l("sah.sah", "chg.mdi", "÷", 1)] : []),
    ...(input.forecast.mo > 0 ? [l("sah.sah", "chg.mo", "÷", 1)] : []),
    ...(input.forecast.pdr > 0 ? [l("sah.sah", "chg.pdr", "÷", 1)] : []),
    ...(input.forecast.absence > 0 ? [l("sah.sah", "chg.absence", "÷", 1)] : []),
    l("sah.sah", "chg.unassigned", "− assigned ÷ SAH", 1),
    l("chg.chgHours", "chg.unassigned", "SAH − Σ", 1),
    l("input.targetPct", "chg.target", "=", 1),
    l("chg.chg", "chg.gap", "−", 2),
    l("chg.target", "chg.gap", "−", 1),

    ...input.budgetLinks,
  ];

  return {
    nodes,
    links,
    meta: {
      resourceName: input.resource.displayName,
      resourceEid: input.resource.eid,
      month: input.month,
      assignmentCount: input.assignmentCount,
      countryCode: input.resource.country?.code ?? null,
      countryName: input.resource.country?.name ?? null,
      federalState: input.resource.federalState ?? null,
      metroCityName: input.resource.metroCity?.name ?? null,
      resolvedHolidays: input.resolvedHolidays.map((holiday) => ({
        date: holiday.date,
        name: holiday.name,
        scope: holiday.scope,
        calendarName: holiday.calendarName,
        sourceType: holiday.sourceType,
      })),
      factors: {
        fte: input.resource.fte,
        targetPct: input.targetPct * 100,
        weeklyAvailability: input.weeklyAvailability,
        baseWorkingDays: input.baseWorkingDays,
        effectiveWorkingDays: input.effectiveWorkingDays,
        baseAvailableHours: input.baseAvailableHours,
        effectiveAvailableHours: input.effectiveAvailableHours,
        publicHolidayCount: input.publicHolidayCount,
        publicHolidayWorkdayCount: input.publicHolidayWorkdayCount,
        publicHolidayHoursDeduction: input.publicHolidayHoursDeduction,
        absenceDayCount: input.absenceCount,
        absenceDayEquivalent: input.absenceDayEquivalent,
        absenceHoursDeduction: input.absenceHoursDeduction,
        bookedHours: input.totalAllocHours,
        chargeableHours: input.chargeableHours,
        chargeabilityPct: input.forecast.chg * 100,
        utilizationPct: input.utilizationPct,
      },
      assignments: input.assignmentBreakdown.map((assignment) => ({
        ...assignment,
        bookedHours: Number(assignment.bookedHours.toFixed(1)),
      })),
    },
  };
}
