"use client";

import { useContext, useMemo, useState, type CSSProperties } from "react";
import { Chart as ReactChart } from "react-chartjs-2";
import ChartJS from "chart.js/auto";
import type { ChartType, Plugin } from "chart.js";
import { colors, dailyOnboardingTarget, rolling, targetCpa } from "@/data/dashboard";
import type { AppsFlyerDailyFunnelResponse, AppsFlyerDailyFunnelRow } from "@/lib/daily-control-cards";
import type { CampaignPerformance } from "@/lib/campaign-performance";
import { calculateCampaignBudgetShare, calculateCampaignCpa } from "@/lib/campaign-performance";
import type { MonthlyProjectionCharts } from "@/lib/monthly-projection-cards";
import { getDaysInMonth, getMonthlyBudget } from "@/lib/monthly-projection-cards";
import { buildWeeklyPeriod, selectDateWindow } from "@/lib/dashboard-periods";
import { ChartValueLabelsContext } from "./ChartCard";
import type { TrackedEventPeriod, TrackedEventsDay, TrackedEventsResponse } from "@/lib/tracked-events";
import type { PaidOnboardingByPlatform } from "@/lib/paid-onboarding-daily";

type LabelElement = { skip?: boolean; tooltipPosition: (useFinalPosition?: boolean) => { x: number; y: number } };

const roundedRect = (context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number) => {
  const right = x + width;
  const bottom = y + height;
  context.beginPath();
  context.moveTo(x + radius, y);
  context.lineTo(right - radius, y);
  context.quadraticCurveTo(right, y, right, y + radius);
  context.lineTo(right, bottom - radius);
  context.quadraticCurveTo(right, bottom, right - radius, bottom);
  context.lineTo(x + radius, bottom);
  context.quadraticCurveTo(x, bottom, x, bottom - radius);
  context.lineTo(x, y + radius);
  context.quadraticCurveTo(x, y, x + radius, y);
  context.closePath();
};

const valueFormatter = new Intl.NumberFormat("fr-FR", { notation: "compact", maximumFractionDigits: 1 });
const valueLabelsPlugin: Plugin<ChartType> = {
  id: "value-label-backgrounds",
  afterDatasetsDraw(chartInstance) {
    if (chartInstance.canvas.closest("[data-show-chart-values='false']")) return;
    const { ctx, chartArea } = chartInstance;
    const styles = getComputedStyle(chartInstance.canvas);
    const background = styles.getPropertyValue("--card").trim() || "#fff";
    const foreground = styles.getPropertyValue("--ink").trim() || "#08302c";
    const border = styles.getPropertyValue("--line").trim() || "#dde7e4";

    chartInstance.data.datasets.forEach((dataset, datasetIndex) => {
      const metadata = chartInstance.getDatasetMeta(datasetIndex);
      if (metadata.hidden) return;
      metadata.data.forEach((rawElement, index) => {
        const rawValue = dataset.data[index];
        if (rawValue === null || rawValue === undefined || typeof rawValue === "object") return;
        const value = Number(rawValue);
        if (!Number.isFinite(value)) return;
        const element = rawElement as LabelElement;
        if (element.skip) return;
        const position = element.tooltipPosition(true);
        const text = valueFormatter.format(value);
        ctx.save();
        ctx.font = "600 9px IBM Plex Mono, monospace";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        const paddingX = 4;
        const height = 16;
        const width = ctx.measureText(text).width + paddingX * 2;
        const centerX = Math.min(chartArea.right - width / 2, Math.max(chartArea.left + width / 2, position.x));
        const centerY = Math.min(chartArea.bottom - height / 2, Math.max(chartArea.top + height / 2, position.y));
        const left = centerX - width / 2;
        const top = centerY - height / 2;
        roundedRect(ctx, left, top, width, height, 4);
        ctx.fillStyle = background;
        ctx.fill();
        ctx.strokeStyle = border;
        ctx.lineWidth = 1;
        ctx.stroke();
        ctx.fillStyle = foreground;
        ctx.fillText(text, centerX, centerY + 0.5);
        ctx.restore();
      });
    });
  },
};

ChartJS.register(valueLabelsPlugin);
ChartJS.defaults.color = colors.grey;
ChartJS.defaults.font.family = "IBM Plex Sans, sans-serif";
ChartJS.defaults.font.size = 11;

const common = { responsive: true, maintainAspectRatio: false, interaction: { intersect: false as const, mode: "index" as const }, plugins: { legend: { position: "bottom" as const, labels: { boxWidth: 10, boxHeight: 10, usePointStyle: true } } }, scales: { x: { grid: { display: false }, ticks: { maxRotation: 0, maxTicksLimit: 9 } }, y: { beginAtZero: true, grid: { color: colors.line } } } };
function ChartRenderer({ type, data, options }: { type: "bar" | "line" | "doughnut"; data: object; options: object }) {
  const showValues = useContext(ChartValueLabelsContext);
  return <div className="chartCanvas" data-show-chart-values={showValues}><ReactChart type={type} data={data as never} options={{ ...options } as never} /></div>;
}
const chart = (type: "bar" | "line" | "doughnut", data: object, options: object = common) => <ChartRenderer type={type} data={data} options={options} />;

const eventColors = [colors.deep, colors.amber, "#2578db", "#834bd6", colors.red, "#0a9a85", "#d84b56", "#64748b"];

export function EventDistributionChart({ days, since, until }: { days: TrackedEventsDay[]; since: string; until: string }) {
  const { sortedDays, eventNames, totals } = useMemo(() => {
    const orderedDays = days
      .filter((day) => day.date >= since && day.date <= until)
      .sort((a, b) => a.date.localeCompare(b.date));
    const totalsByEvent = new Map<string, number>();
    orderedDays.forEach((day) => day.streams?.forEach((stream) => stream.events?.forEach((event) => {
      totalsByEvent.set(event.event_name, (totalsByEvent.get(event.event_name) ?? 0) + Number(event.active_users ?? 0));
    })));
    const names = [...totalsByEvent.keys()].sort((a, b) => (totalsByEvent.get(b) ?? 0) - (totalsByEvent.get(a) ?? 0) || a.localeCompare(b));
    return { sortedDays: orderedDays, eventNames: names, totals: totalsByEvent };
  }, [days, since, until]);
  const defaults = useMemo(() => eventNames.slice(0, 3), [eventNames]);
  const [selection, setSelection] = useState<string[]>(defaults);
  const selectedEvents = selection.filter((name) => totals.has(name));
  const activeSelection = selectedEvents;
  const toggleEvent = (name: string) => setSelection((current) => current.includes(name) ? current.filter((event) => event !== name) : [...current, name]);
  const labels = sortedDays.map((day) => new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "2-digit" }).format(new Date(`${day.date}T00:00:00`)));
  const dailyValue = (day: TrackedEventsDay, eventName: string) => day.streams?.reduce((dayTotal, stream) => dayTotal + (stream.events?.reduce((streamTotal, event) => streamTotal + (event.event_name === eventName ? Number(event.active_users ?? 0) : 0), 0) ?? 0), 0) ?? 0;

  if (!eventNames.length) return <p className="eventChartEmpty">Aucune donnée d’événement disponible pour cette période.</p>;

  return <div className="eventDistribution">
    <div className="eventSelector">
      <details>
        <summary>{activeSelection.length} événement{activeSelection.length > 1 ? "s" : ""} sélectionné{activeSelection.length > 1 ? "s" : ""}</summary>
        <div className="eventSelectorMenu">
          <div className="eventSelectorActions"><button type="button" onClick={() => setSelection(defaults)}>Top 3</button><button type="button" onClick={() => setSelection([])}>Effacer</button></div>
          <div className="eventOptions">{eventNames.map((name) => <label key={name}><input type="checkbox" checked={selectedEvents.includes(name)} onChange={() => toggleEvent(name)} /><span>{name}</span><small>{valueFormatter.format(totals.get(name) ?? 0)}</small></label>)}</div>
        </div>
      </details>
      <div className="eventSelectionChips">{activeSelection.map((name, index) => <button type="button" key={name} style={{ "--event-color": eventColors[index % eventColors.length] } as CSSProperties} onClick={() => toggleEvent(name)} title={`Retirer ${name}`}>{name}<span aria-hidden="true">×</span></button>)}</div>
    </div>
    <div className="eventChartCanvas">{chart("line", { labels, datasets: activeSelection.map((name, index) => ({ label: name, data: sortedDays.map((day) => dailyValue(day, name)), borderColor: eventColors[index % eventColors.length], backgroundColor: eventColors[index % eventColors.length], borderWidth: 2, pointRadius: sortedDays.length > 31 ? 0 : 2, pointHoverRadius: 4, tension: .25 })) }, common)}</div>
  </div>;
}

const onboardingEventNames = [
  "first_open", "onboarding_start", "otp_requested_phone", "otp_verified_phone", "otp_requested_email", "otp_verified_email",
  "document_scan", "personal_data_confirmation", "face_verification_begin", "address_confirmation", "job_selection",
  "monthly_revenue_selection", "fund_origin_selection", "international_operations", "fatca_status_declared", "card_begin",
  "see_cards", "confirm_card", "card_reception", "card_agency_confirmation", "card_signed", "card_confirmation", "contract_signed",
] as const;

const onboardingStageColors = onboardingEventNames.map((_, index) => index < 6 ? "#2578db" : index < 9 ? "#0a9a85" : index < 13 ? colors.amber : index < 15 ? "#834bd6" : index < 22 ? "#19a7ae" : colors.red);

export function OnboardingFunnelChart({ response }: { response: TrackedEventsResponse }) {
  const [stream, setStream] = useState<"all" | "ios" | "android">("all");
  const events: TrackedEventPeriod[] = stream === "all"
    ? response.events ?? []
    : response.events_by_stream?.[stream]?.events ?? [];
  const metrics = new Map(events.map((event) => [event.event_name, event.active_users]));
  const current = onboardingEventNames.map((name) => Number(metrics.get(name)?.current_value ?? 0));
  const previous = onboardingEventNames.map((name) => Number(metrics.get(name)?.previous_value ?? 0));
  const maximum = Math.max(1, ...current, ...previous);
  const compactEventValue = (value: number) => value >= 1_000_000 ? `${(value / 1_000_000).toFixed(1)}M` : value >= 1_000 ? `${(value / 1_000).toFixed(1)}K` : value.toLocaleString("fr-FR");
  const stepVariation = (index: number) => index === 0 || current[index - 1] <= 0 ? null : (current[index] - current[index - 1]) / current[index - 1] * 100;
  const periodVariation = (index: number) => metrics.get(onboardingEventNames[index])?.variation_percent;
  const columns = [onboardingEventNames.slice(0, 8), onboardingEventNames.slice(8, 15), onboardingEventNames.slice(15)];

  return <article className="onboardingFunnelCard">
    <header className="onboardingFunnelHeader"><div><p>Parcours Onboarding Funnel</p><h3>Saham Bank : Onboarding funnel 23 events</h3></div><label className="funnelStreamFilter"><span>Plateforme</span><select value={stream} onChange={(event) => setStream(event.target.value as "all" | "ios" | "android")}><option value="all">Tous</option><option value="ios">iOS</option><option value="android">Android</option></select></label></header>
    <div className="onboardingFunnelScroll">
      <div className="onboardingFunnelInner">
        <div className="funnelStages"><span className="blue">Vérification</span><span className="green">ID</span><span className="orange">Détails</span><span className="purple">Conformité</span><span className="cyan">Choix du forfait et carte</span><span className="red">Signature du contrat</span></div>
        <div className="funnelBars">
          {onboardingEventNames.map((name, index) => {
            const loss = stepVariation(index);
            const height = current[index] / maximum * 78;
            const previousHeight = previous[index] / maximum * 78;
            return <div className={`funnelBarGroup ${index === 15 ? "cardFlowStart" : ""}`} key={name} style={{ "--bar-height": `${height}%`, "--previous-height": `${previousHeight}%`, "--bar-color": onboardingStageColors[index] } as CSSProperties} title={`${name} · actuel ${current[index].toLocaleString("fr-FR")} · précédent ${previous[index].toLocaleString("fr-FR")}`}>
              <b>{compactEventValue(current[index])}</b><i aria-label={`Période précédente : ${previous[index].toLocaleString("fr-FR")}`} /><span className="funnelBar" /><strong>{index + 1}</strong><em>{loss !== null && loss < 0 ? `${Math.round(loss)}%` : ""}</em>
            </div>;
          })}
        </div>
        <div className="funnelDetailGrid">{columns.map((column, columnIndex) => <div className="funnelDetailColumn" key={columnIndex}>{column.map((name) => { const index = onboardingEventNames.indexOf(name); const variation = periodVariation(index); return <div className="funnelDetailRow" key={name}><span><i />{index + 1}</span><small>{name}</small><b>{current[index].toLocaleString("fr-FR")} {variation != null && <em className={variation < 0 ? "negative" : "positive"}>({variation >= 0 ? "+" : ""}{Math.round(variation)}%)</em>}</b></div>; })}</div>)}</div>
      </div>
    </div>
  </article>;
}

export function DailySpendChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const spendLabels = sortedData.map((day) => new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "2-digit" }).format(new Date(`${day.date}T00:00:00`)));

  return chart("bar", { labels: spendLabels, datasets: [
  { label: "Google Ads", data: sortedData.map((day) => Number(day.google_spend ?? 0)), backgroundColor: colors.deep, stack: "spend" },
  { label: "Meta Ads", data: sortedData.map((day) => Number(day.meta_spend ?? 0)), backgroundColor: colors.amber, stack: "spend" },
  { label: "Rythme cible", data: sortedData.map((day) => { const budget = getMonthlyBudget(day.date); const days = getDaysInMonth(day.date); return budget !== null && days > 0 ? budget / days : null; }), type: "line", borderColor: colors.red, borderDash: [5,4], borderWidth: 1.5, pointRadius: 0 },
]}, { ...common, scales: { x: { ...common.scales.x, stacked: true }, y: { ...common.scales.y, stacked: true } } }); }

export function PlatformShareChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const lastDay = [...data]
    .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
    .at(-1);
  const googleSpend = Number(lastDay?.google_spend ?? 0);
  const metaSpend = Number(lastDay?.meta_spend ?? 0);
  const totalSpend = googleSpend + metaSpend;
  const dateLabel = lastDay
    ? new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "short" }).format(new Date(`${lastDay.date}T00:00:00`))
    : "Dernier jour";

  return chart("doughnut", {
    labels: ["Google Ads", "Meta Ads"],
    datasets: [{ data: [googleSpend, metaSpend], backgroundColor: [colors.deep, colors.amber], borderWidth: 0 }],
  }, {
    responsive: true,
    maintainAspectRatio: false,
    cutout: "62%",
    plugins: {
      legend: common.plugins.legend,
      tooltip: {
        callbacks: {
          title: () => dateLabel,
          label: (context: { label?: string; parsed: number }) => {
            const percentage = totalSpend > 0 ? context.parsed / totalSpend * 100 : 0;
            return `${context.label}: ${Math.round(context.parsed).toLocaleString("fr-FR")} MAD (${percentage.toFixed(1)} %)`;
          },
        },
      },
    },
  });
}

export function SuccessChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const successLabels = sortedData.map((day) => new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "2-digit" }).format(new Date(`${day.date}T00:00:00`)));
  const dailySuccesses = sortedData.map((day) => Number(day.onboarding_success_active_users ?? 0));
  const movingAverage7d = rolling(dailySuccesses, 7);
  const incompleteWindowStart = Math.max(0, dailySuccesses.length - 7);

  return chart("bar", { labels: successLabels, datasets: [
    { label: "Réussites consolidées", data: dailySuccesses.map((value, index) => index < incompleteWindowStart ? value : null), backgroundColor: colors.teal },
    { label: "Fenêtre incomplète", data: dailySuccesses.map((value, index) => index >= incompleteWindowStart ? value : null), backgroundColor: "#B9D6D0" },
    { label: "Moyenne mobile 7 j", data: movingAverage7d, type: "line", borderColor: colors.deep, borderWidth: 2, pointRadius: 0, tension: .35 },
  ]}, {
    ...common,
    plugins: {
      ...common.plugins,
      tooltip: {
        callbacks: {
          label: (context: { dataset: { label?: string }; parsed: { y: number | null } }) => {
            if (context.parsed.y === null) return "";
            return `${context.dataset.label}: ${context.parsed.y.toFixed(context.dataset.label === "Moyenne mobile 7 j" ? 1 : 0)}`;
          },
        },
      },
    },
  });
}

export function CpaChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const cpaLabels = sortedData.map((day) => new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "2-digit" }).format(new Date(`${day.date}T00:00:00`)));
  const rollingCpa7d = sortedData.map((_, index) => {
    const window = sortedData.slice(Math.max(0, index - 6), index + 1);
    const spend = window.reduce((total, day) => total + Number(day.total_spend ?? 0), 0);
    const success = window.reduce((total, day) => total + Number(day.onboarding_success_active_users ?? 0), 0);
    return success > 0 ? spend / success : null;
  });
  const incompleteWindowStart = Math.max(0, rollingCpa7d.length - 7);

  return chart("line", { labels: cpaLabels, datasets: [
    { label: "Coût glissant 7 j", data: rollingCpa7d.map((value, index) => index < incompleteWindowStart ? value : null), borderColor: colors.deep, backgroundColor: "rgba(11,79,74,.06)", fill: true, pointRadius: 0, tension: .3 },
    { label: "Fenêtre incomplète", data: rollingCpa7d.map((value, index) => index >= incompleteWindowStart ? value : null), borderColor: colors.grey, borderDash: [5,4], borderWidth: 2, pointRadius: 2, pointBackgroundColor: rollingCpa7d.map((value) => value !== null && value > 3000 ? colors.red : value !== null && value > dailyOnboardingTarget ? colors.amber : colors.deep), tension: .3 },
    { label: "Cible 750 MAD", data: sortedData.map(() => dailyOnboardingTarget), borderColor: colors.amber, borderDash: [5,4], borderWidth: 1.5, pointRadius: 0 },
    { label: "Arbitrage 3 000 MAD", data: sortedData.map(() => 3000), borderColor: colors.red, borderDash: [2,3], borderWidth: 1.5, pointRadius: 0 },
  ]}, {
    ...common,
    plugins: {
      ...common.plugins,
      tooltip: {
        callbacks: {
          label: (context: { dataset: { label?: string }; parsed: { y: number | null } }) => context.parsed.y === null ? "N/A" : `${context.dataset.label}: ${Math.round(context.parsed.y).toLocaleString("fr-FR")} MAD`,
        },
      },
    },
  });
}

export function InstallsChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const installLabels = sortedData.map((row) => row.date);

  return chart("bar", { labels: installLabels, datasets: [
    { label: "Android", data: sortedData.map((row) => Number(row.appsflyer_installs_android ?? 0)), backgroundColor: colors.grey, stack: "os" },
    { label: "iOS", data: sortedData.map((row) => Number(row.appsflyer_installs_ios ?? 0)), backgroundColor: colors.deep, stack: "os" },
  ]}, { ...common, scales: { x: { ...common.scales.x, stacked: true }, y: { ...common.scales.y, stacked: true } } });
}

export function AppsFlyerEvolutionChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => a.date.localeCompare(b.date));

  return chart("line", { labels: sortedData.map((row) => row.date), datasets: [
    { label: "Installs", data: sortedData.map((row) => Number(row.appsflyer_installs_total ?? 0)), borderColor: colors.deep, backgroundColor: colors.deep, borderWidth: 2.2, pointRadius: 2, tension: .3 },
    { label: "Onboarding start", data: sortedData.map((row) => Number(row.onboarding_start_active_users ?? 0)), borderColor: colors.teal, backgroundColor: colors.teal, borderWidth: 2.2, pointRadius: 2, tension: .3 },
    { label: "Onboarding success", data: sortedData.map((row) => Number(row.onboarding_success_active_users ?? 0)), borderColor: colors.amber, backgroundColor: colors.amber, borderWidth: 2.2, pointRadius: 2, tension: .3 },
  ]});
}

function FunnelRateChart({ data, step }: { data: AppsFlyerDailyFunnelRow[]; step: "install-to-start" | "start-to-success" }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const funnelLabels = sortedData.map((row) => row.date);
  const rate = sortedData.map((row) => {
    const source = step === "install-to-start"
      ? Number(row.appsflyer_installs_total ?? 0)
      : Number(row.onboarding_start_active_users ?? 0);
    const destination = step === "install-to-start"
      ? Number(row.onboarding_start_active_users ?? 0)
      : Number(row.onboarding_success_active_users ?? 0);
    return source > 0 ? Number((destination / source * 100).toFixed(1)) : null;
  });
  const label = step === "install-to-start" ? "Install → démarrage (%)" : "Démarrage → réussite (%)";
  const borderColor = step === "install-to-start" ? colors.teal : colors.amber;

  return chart("line", { labels: funnelLabels, datasets: [
    { label, data: rate, borderColor, pointRadius: 0, tension: .35 },
  ]});
}

export function InstallToStartRateChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  return <FunnelRateChart data={data} step="install-to-start" />;
}

export function StartToSuccessRateChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  return <FunnelRateChart data={data} step="start-to-success" />;
}

export function CostEvolutionChart({ data }: { data: AppsFlyerDailyFunnelRow[] }) {
  const sortedData = [...data].sort((a, b) => a.date.localeCompare(b.date));
  const labels = sortedData.map((row) => new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "short" }).format(new Date(`${row.date}T00:00:00`)));
  const successCosts = sortedData.map((row) => {
    const success = Number(row.onboarding_success_active_users ?? 0);
    return success > 0 ? Number(row.total_spend ?? 0) / success : null;
  });
  const cdcCosts = sortedData.map((row) => {
    const cdc = Number(row.cdc_accounts ?? 0);
    return cdc > 0 ? Number(row.total_spend ?? 0) / cdc : null;
  });
  const hasDailyCdc = cdcCosts.some((value) => value !== null);

  return <div className="costEvolutionChart">
    <div className="costEvolutionCanvas">{chart("line", { labels, datasets: [
      { label: "Coût / réussite onboarding (MAD)", data: successCosts, borderColor: "#245aa5", backgroundColor: "#245aa5", borderWidth: 2.2, pointRadius: 0, pointHoverRadius: 4, tension: .35 },
      ...(hasDailyCdc ? [{ label: "Coût / CDC (MAD)", data: cdcCosts, borderColor: "#24a873", backgroundColor: "#24a873", borderWidth: 2.2, pointRadius: 0, pointHoverRadius: 4, tension: .35 }] : []),
    ]}, { ...common, plugins: { ...common.plugins, tooltip: { callbacks: { label: (context: { dataset: { label?: string }; parsed: { y: number | null } }) => `${context.dataset.label}: ${context.parsed.y === null ? "N/A" : `${Math.round(context.parsed.y).toLocaleString("fr-FR")} MAD`}` } } }, scales: { x: common.scales.x, y: { ...common.scales.y, ticks: { callback: (value: string | number) => `${valueFormatter.format(Number(value))} MAD` } } } })}</div>
    {!hasDailyCdc && <p className="costEvolutionNotice">La courbe « Coût / CDC » apparaîtra lorsque l’API fournira <code>cdc_accounts</code> pour chaque journée.</p>}
  </div>;
}

function OverviewDonut({ title, subtitle, labels, values, colors: donutColors, totalLabel }: { title: string; subtitle: string; labels: string[]; values: number[]; colors: string[]; totalLabel: string }) {
  const total = values.reduce((sum, value) => sum + value, 0);
  return <article className="overviewDonut"><header><h3>{title}</h3><p>{subtitle}</p></header><div className="overviewDonutVisual"><ChartValueLabelsContext.Provider value={false}>{chart("doughnut", { labels, datasets: [{ data: values, backgroundColor: donutColors, borderColor: "#fff", borderWidth: 2 }] }, { responsive: true, maintainAspectRatio: false, cutout: "64%", plugins: { legend: { display: false }, tooltip: { callbacks: { label: (context: { label?: string; parsed: number }) => `${context.label}: ${valueFormatter.format(context.parsed)} (${total > 0 ? (context.parsed / total * 100).toFixed(1) : "0"} %)` } } } })}</ChartValueLabelsContext.Provider><div className="overviewDonutCenter"><strong>{valueFormatter.format(total)}</strong><span>{totalLabel}</span></div></div><div className="overviewDonutLegend">{labels.map((label, index) => <div key={label}><i style={{ background: donutColors[index] }} /><span>{label}</span><b>{valueFormatter.format(values[index])}</b><small>{total > 0 ? (values[index] / total * 100).toFixed(1) : "0"} %</small></div>)}</div></article>;
}

export function OverviewDistributionDonuts({ response, paidOnboarding }: { response: AppsFlyerDailyFunnelResponse; paidOnboarding: PaidOnboardingByPlatform }) {
  const total = response.total ?? {};
  const metric = (key: string) => typeof total[key] === "number" ? Number(total[key]) : 0;
  const paidMetric = (platform: "meta" | "google", key: "onboarding_start" | "onboarding_success") => Number(paidOnboarding[platform].totals?.[key] ?? 0);
  return <section className="overviewDonutGrid" aria-label="Répartitions par plateforme">
    <OverviewDonut title="Dépenses par plateforme" subtitle="Répartition des dépenses publicitaires" labels={["Google Ads", "Meta Ads"]} values={[metric("google_spend"), metric("meta_spend")]} colors={["#2684ed", "#e74f70"]} totalLabel="MAD dépensés" />
    <OverviewDonut title="Démarrages onboarding" subtitle="Répartition par plateforme publicitaire" labels={["Google Ads", "Meta Ads"]} values={[paidMetric("google", "onboarding_start"), paidMetric("meta", "onboarding_start")]} colors={["#2684ed", "#e74f70"]} totalLabel="démarrages" />
    <OverviewDonut title="Réussites onboarding" subtitle="Répartition par plateforme publicitaire" labels={["Google Ads", "Meta Ads"]} values={[paidMetric("google", "onboarding_success"), paidMetric("meta", "onboarding_success")]} colors={["#2684ed", "#e74f70"]} totalLabel="réussites" />
  </section>;
}

function sessionStartsByDevice(response: TrackedEventsResponse, period: "current_value" | "previous_value") {
  let desktop = 0;
  let mobile = 0;

  for (const [streamName, stream] of Object.entries(response.events_by_stream ?? {})) {
    const sessionStart = stream.events?.find((event) => event.event_name === "session_start");
    const value = Number(sessionStart?.active_users?.[period] ?? 0);
    const normalizedName = streamName.toLocaleLowerCase("fr-FR");

    if (normalizedName.includes("ios") || normalizedName.includes("android") || normalizedName.includes("mobile")) {
      mobile += value;
    } else if (normalizedName.includes("web") || normalizedName.includes("desktop") || normalizedName.includes("saham.com")) {
      desktop += value;
    }
  }

  return [desktop, mobile];
}

export function SeoDeviceTrafficPies({ response }: { response: TrackedEventsResponse }) {
  const current = sessionStartsByDevice(response, "current_value");
  const previous = sessionStartsByDevice(response, "previous_value");

  return <section className="seoTrafficSection" aria-labelledby="seo-traffic-title">
    <header className="seoTrafficHeader">
      <p>SEO</p>
      <h2 id="seo-traffic-title">Trafic Desktop vs Mobile</h2>
      <span>Utilisateurs ayant démarré une session, répartis par type d’appareil</span>
    </header>
    <div className="seoTrafficPies">
      <OverviewDonut title="Période sélectionnée" subtitle="Répartition actuelle" labels={["Desktop", "Mobile"]} values={current} colors={["#2684ed", "#24a873"]} totalLabel="utilisateurs" />
      <OverviewDonut title="Période précédente" subtitle="Point de comparaison" labels={["Desktop", "Mobile"]} values={previous} colors={["#2684ed", "#24a873"]} totalLabel="utilisateurs" />
    </div>
  </section>;
}

export function CampaignCpaChart({ campaigns }: { campaigns: CampaignPerformance[] }) {
  const rankedCampaigns = [...campaigns]
    .map((campaign) => ({ campaign, cpa: calculateCampaignCpa(campaign) }))
    .filter((entry): entry is { campaign: CampaignPerformance; cpa: number } => entry.cpa !== null)
    .sort((a, b) => a.cpa - b.cpa);
  return chart("bar", {
    labels: rankedCampaigns.map(({ campaign }) => campaign.campaign_name),
    datasets: [{
      label: "Coût par onboarding (MAD)",
      data: rankedCampaigns.map(({ cpa }) => cpa),
      backgroundColor: rankedCampaigns.map(({ cpa }) => {
        return cpa <= targetCpa ? colors.deep : cpa < 5000 ? colors.amber : colors.red;
      }),
    }],
  }, { ...common, indexAxis: "y", plugins: { legend: { display: false } }, scales: { x: common.scales.y, y: { grid: { display: false } } } });
}
export function BudgetShareChart({ campaigns }: { campaigns: CampaignPerformance[] }) {
  const { underTargetPct, aboveTargetPct, unmatchedPct } = calculateCampaignBudgetShare(campaigns);
  const oneDecimal = (value: number) => Number(value.toFixed(1));

  return chart("doughnut", { labels: ["Sous la cible", "Au-dessus", "Non rapproché"], datasets: [{ data: [oneDecimal(underTargetPct), oneDecimal(aboveTargetPct), oneDecimal(unmatchedPct)], backgroundColor: [colors.deep, colors.red, colors.soft], borderWidth: 0 }] }, { responsive: true, maintainAspectRatio: false, cutout: "58%", plugins: common.plugins });
}
export function OsShareChart({ data, until }: { data: AppsFlyerDailyFunnelRow[]; until: string }) {
  const sortedData = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
  const period = buildWeeklyPeriod(until);
  const { rows: last7Days } = selectDateWindow(sortedData, period.since, period.until);
  const installsAndroid7d = last7Days.reduce((sum, row) => sum + Number(row.appsflyer_installs_android || 0), 0);
  const installsIos7d = last7Days.reduce((sum, row) => sum + Number(row.appsflyer_installs_ios || 0), 0);
  const totalInstalls7d = installsAndroid7d + installsIos7d;
  const successAndroid7d = last7Days.reduce((sum, row) => sum + Number(row.onboarding_success_active_users_android || 0), 0);
  const successIos7d = last7Days.reduce((sum, row) => sum + Number(row.onboarding_success_active_users_ios || 0), 0);
  const totalSuccess7d = successAndroid7d + successIos7d;
  const oneDecimal = (value: number) => Number(value.toFixed(1));
  const androidVolumeShare = oneDecimal(totalInstalls7d > 0 ? installsAndroid7d / totalInstalls7d * 100 : 0);
  const iosVolumeShare = oneDecimal(totalInstalls7d > 0 ? installsIos7d / totalInstalls7d * 100 : 0);
  const androidSuccessShare = oneDecimal(totalSuccess7d > 0 ? successAndroid7d / totalSuccess7d * 100 : 0);
  const iosSuccessShare = oneDecimal(totalSuccess7d > 0 ? successIos7d / totalSuccess7d * 100 : 0);

  return chart("bar", { labels: ["Part des installs", "Part des réussites"], datasets: [{ label: "Android", data: [androidVolumeShare, androidSuccessShare], backgroundColor: colors.grey, stack: "os" }, { label: "iOS", data: [iosVolumeShare, iosSuccessShare], backgroundColor: colors.deep, stack: "os" }] }, { ...common, indexAxis: "y", scales: { x: { ...common.scales.y, stacked: true, max: 100 }, y: { grid: { display: false }, stacked: true } } });
}
export function PlanAccountsChart({ data }: { data: MonthlyProjectionCharts["cdcVsPlan"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Objectif du plan", data: data.target, backgroundColor: colors.soft }, { label: "Comptes CDC réalisés", data: data.actual, backgroundColor: colors.deep }] }); }
export function MonthlyCpaCdcChart({ data }: { data: MonthlyProjectionCharts["cpaCdcVsTarget"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Coût par CDC (MAD)", data: data.actual, backgroundColor: colors.deep }, { label: "Objectif 1 500 MAD", data: data.target, type: "line", borderColor: colors.red, borderDash: [5,4], borderWidth: 2, pointRadius: 0 }] }); }
export function PlanOnboardingSuccessChart({ data }: { data: MonthlyProjectionCharts["onboardingSuccessVsPlan"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Objectif du plan", data: data.target, backgroundColor: colors.soft }, { label: "Onboardings réussis", data: data.actual, backgroundColor: colors.deep }] }); }
export function MonthlyOnboardingSuccessCostChart({ data }: { data: MonthlyProjectionCharts["costPerOnboardingSuccessVsTarget"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Coût par Onboarding Success (MAD)", data: data.actual, backgroundColor: colors.deep }, { label: "Objectif 750 MAD", data: data.target, type: "line", borderColor: colors.red, borderDash: [5,4], borderWidth: 2, pointRadius: 0 }] }); }
export function CdcRatioChart({ data }: { data: MonthlyProjectionCharts["cdcRatio"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Ratio CDC réel", data: data.actual, backgroundColor: colors.deep }, { label: "Hypothèse du plan", data: data.target, type: "line", borderColor: colors.red, borderDash: [5,4], pointRadius: 0 }] }, { ...common, scales: { x: common.scales.x, y: { ...common.scales.y, max: 60 } } }); }
export function MonthlyBudgetChart({ data }: { data: MonthlyProjectionCharts["budgetVsPlan"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Budget planifié", data: data.planned, backgroundColor: colors.soft }, { label: "Budget consommé", data: data.actual, backgroundColor: colors.teal }] }); }
export function CrmFunnelChart({ data }: { data: MonthlyProjectionCharts["funnel"] }) { return chart("bar", { labels: data.labels, datasets: [{ label: "Dossiers", data: data.values, backgroundColor: [colors.grey,colors.mint,colors.grey,colors.grey,colors.mint,colors.deep] }] }, { ...common, indexAxis: "y", plugins: { legend: { display: false } }, scales: { x: common.scales.y, y: { grid: { display: false } } } }); }
