import React, { useEffect, useMemo, useState } from 'react';

import InfoTooltip from '../agent-analytics/InfoTooltip';
import type {
  ArticlePromptImpactResponse,
  PromptImpactEntry,
} from '../../service/visibility/visibility.interface';
import { getArticlePromptImpact } from '../../service/visibility/visibility.service';

import {
  PROMPT_TREND_STATUS_META,
  PromptSparkline,
  TrendStatusIcon,
  VISIBILITY_SCORE_HELP,
  trendChange,
} from './PromptTrend';
import { toneToBadgeClass } from './helpers';

interface PromptImpactPanelProps {
  /** Recomaze client id. */
  clientId: string;
  /** Recomaze JWT. */
  token: string;
  /** Article whose prompt impact to load. */
  articleId: string;
}

/** Aggregate read off the article's tracked prompts for the hero + verdict. */
interface ImpactSummary {
  trackedCount: number;
  avgScore: number | null;
  avgBaseline: number | null;
  avgDelta: number | null;
  improving: number;
  declining: number;
  collecting: number;
}

/**
 * Roll the per-prompt entries into the headline numbers and the verdict copy.
 *
 * @param {PromptImpactEntry[]} prompts - Per-prompt impact entries.
 * @returns {ImpactSummary} Aggregate counts and averages.
 */
function summarize(prompts: PromptImpactEntry[]): ImpactSummary {
  const tracked = prompts.filter(
    entry => entry.tracked && entry.current_score !== null
  );
  const scored = tracked.map(entry => entry.current_score as number);
  const baselines = tracked
    .map(entry => entry.baseline_score)
    .filter((score): score is number => score !== null);
  const withDelta = tracked
    .map(entry => entry.delta_score)
    .filter((delta): delta is number => delta !== null);
  const avg = (values: number[]): number | null =>
    values.length === 0
      ? null
      : Math.round(
          values.reduce((sum, value) => sum + value, 0) / values.length
        );

  return {
    trackedCount: tracked.length,
    avgScore: avg(scored),
    avgBaseline: avg(baselines),
    avgDelta: avg(withDelta),
    improving: tracked.filter(entry => entry.status === 'improving').length,
    declining: tracked.filter(entry => entry.status === 'declining').length,
    collecting: prompts.filter(entry => entry.status === 'collecting').length,
  };
}

/**
 * Pick a friendly one-line verdict from the aggregate.
 *
 * @param {ImpactSummary} summary - Aggregate counts and averages.
 * @returns {string} A human verdict sentence.
 */
function verdictFor(summary: ImpactSummary): string {
  if (summary.trackedCount === 0 || summary.avgDelta === null) {
    return 'Too early to tell. Impact shows up here after the next weekly scan.';
  }
  if (summary.improving > summary.declining && summary.avgDelta > 0) {
    return 'Paying off. Visibility is climbing on the prompts this article targets.';
  }
  if (summary.declining > summary.improving && summary.avgDelta < 0) {
    return 'Visibility dipped since publish. The content may need a refresh.';
  }

  return 'Mixed so far. Some prompts moved up, others slipped.';
}

/** Inline right-arrow glyph for the before -> after readout. */
const ArrowRightIcon = ({ size = 12 }: { size?: number }): JSX.Element => (
  <svg
    width={size}
    height={size}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={2}
    strokeLinecap="round"
    strokeLinejoin="round"
    aria-hidden="true"
  >
    <line x1="5" y1="12" x2="19" y2="12" />
    <polyline points="12 5 19 12 12 19" />
  </svg>
);

/**
 * Render one targeted prompt's impact row with a before -> after readout.
 *
 * @param {object} props - Component props.
 * @param {PromptImpactEntry} props.entry - The per-prompt impact entry.
 * @param {string | null} props.publishedWeek - Week to dot on the sparkline.
 * @returns {JSX.Element} The row element.
 */
const PromptImpactRow = ({
  entry,
  publishedWeek,
}: {
  entry: PromptImpactEntry;
  publishedWeek: string | null;
}): JSX.Element => {
  if (!entry.tracked) {
    return (
      <div className="flex flex-col gap-0.5 border-t border-gray-200 py-3 first:border-t-0 first:pt-0">
        <p className="text-sm text-gray-900">{entry.prompt_text}</p>
        <p className="text-xs text-gray-500">
          Not in your tracked set yet. Add it on the prompts page to start
          measuring this one.
        </p>
      </div>
    );
  }

  const meta = PROMPT_TREND_STATUS_META[entry.status];
  const hasBeforeAfter =
    entry.baseline_score !== null &&
    entry.baseline_score > 0 &&
    entry.current_score !== null;
  const change = trendChange(
    entry.baseline_score,
    entry.current_score,
    entry.delta_score
  );
  const tone = change.kind === 'delta' ? meta.className : 'text-gray-500';

  return (
    <div className="flex items-center justify-between gap-3 border-t border-gray-200 py-3 first:border-t-0 first:pt-0">
      <div className="min-w-0 flex-1">
        <p
          className="truncate text-sm font-medium text-gray-900"
          title={entry.prompt_text}
        >
          {entry.prompt_text}
        </p>
        {hasBeforeAfter ? (
          <span className="inline-flex items-center gap-1.5 text-xs text-gray-500">
            <span>{entry.baseline_score}</span>
            <ArrowRightIcon />
            <span className="font-semibold text-gray-900">
              {entry.current_score}
            </span>
            <span>since publish</span>
          </span>
        ) : (
          <span className="text-xs text-gray-500">
            {change.kind === 'new'
              ? `Now surfacing you at ${entry.current_score}/100 since publish`
              : entry.current_score !== null
                ? `Now ${entry.current_score}/100 · baseline lands after the next scan`
                : 'First scan after publish pending'}
          </span>
        )}
      </div>
      <div className={`flex items-center gap-3 ${tone}`}>
        <PromptSparkline
          series={entry.series}
          width={96}
          height={28}
          markerWeek={publishedWeek}
        />
        <span className="inline-flex w-14 items-center justify-end gap-1 text-sm font-semibold">
          {change.kind === 'new' ? (
            <span className="text-green-600">New</span>
          ) : change.kind === 'delta' ? (
            <>
              <TrendStatusIcon status={entry.status} size={14} />
              {`${change.value > 0 ? '+' : ''}${change.value}`}
            </>
          ) : (
            <span className="text-gray-500">—</span>
          )}
        </span>
      </div>
    </div>
  );
};

/**
 * Article "Prompt impact" panel (Beta). Leads with the average visibility
 * across the prompts this article targets and a plain-language verdict, then
 * breaks it down per prompt with a before -> after readout and a sparkline
 * dotted at the publish week. Correlation only - it marks publish, it does not
 * claim the article caused the change. Fetches client-side so it never blocks
 * the article render; stays silent when the article targets no prompts.
 *
 * @param {PromptImpactPanelProps} props - Component props.
 * @returns {JSX.Element | null} The panel, or ``null`` when there is nothing
 *     to show.
 */
export const PromptImpactPanel = ({
  clientId,
  token,
  articleId,
}: PromptImpactPanelProps): JSX.Element | null => {
  const [data, setData] = useState<ArticlePromptImpactResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    getArticlePromptImpact(clientId, token, articleId)
      .then(result => {
        if (!cancelled) setData(result);
      })
      .catch(err => {
        if (!cancelled) {
          setError(
            err instanceof Error ? err.message : 'Failed to load impact.'
          );
        }
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, [clientId, token, articleId]);

  const summary = useMemo(
    () => (data ? summarize(data.prompts) : null),
    [data]
  );

  // Nothing to show: no targeted prompts at all. Stay invisible rather than
  // render an empty card on manual articles with no prompt linkage.
  if (!loading && !error && (!data || data.prompts.length === 0)) {
    return null;
  }

  const heroChange = summary
    ? trendChange(summary.avgBaseline, summary.avgScore, summary.avgDelta)
    : ({ kind: 'none' } as const);

  return (
    <div className="flex flex-col gap-4 rounded-xl border border-gray-200 bg-white p-5">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <div className="flex items-center gap-2">
          <h3 className="text-base font-semibold text-gray-900">
            Prompt impact
          </h3>
          <span
            className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass('warning')}`}
          >
            Beta
          </span>
        </div>
        {data?.published_week && (
          <span className="text-xs text-gray-500">
            {`Published ${data.published_week}`}
          </span>
        )}
      </div>

      {loading && (
        <div className="flex flex-col gap-2 animate-pulse">
          <div className="h-16 w-full rounded bg-gray-200" />
          <div className="h-4 w-1/2 rounded bg-gray-200" />
        </div>
      )}

      {error && <p className="text-sm text-red-600">{error}</p>}

      {!loading && !error && data && summary && (
        <>
          <div className="flex items-center gap-4 rounded-xl border border-gray-200 bg-gray-50 p-4">
            <div className="flex flex-col">
              <span className="inline-flex items-center gap-1 text-[11px] font-medium uppercase tracking-wide text-gray-500">
                Avg visibility
                <InfoTooltip text={VISIBILITY_SCORE_HELP} />
              </span>
              <div className="flex items-baseline gap-2">
                <span className="text-3xl font-bold text-gray-900">
                  {summary.avgScore ?? '—'}
                </span>
                {heroChange.kind === 'new' && (
                  <span className="text-sm font-semibold text-green-600">
                    New
                  </span>
                )}
                {heroChange.kind === 'delta' && (
                  <span
                    className={`inline-flex items-center gap-0.5 text-sm font-semibold ${
                      heroChange.value > 0
                        ? 'text-green-600'
                        : heroChange.value < 0
                          ? 'text-red-600'
                          : 'text-gray-500'
                    }`}
                  >
                    <TrendStatusIcon
                      status={heroChange.value >= 0 ? 'improving' : 'declining'}
                      size={16}
                    />
                    {`${heroChange.value > 0 ? '+' : ''}${heroChange.value}`}
                  </span>
                )}
              </div>
            </div>
            <div className="flex-1 text-sm text-gray-500">
              <p className="font-medium text-gray-900">{verdictFor(summary)}</p>
              <p className="text-xs">
                {`Across ${summary.trackedCount} tracked prompt${
                  summary.trackedCount === 1 ? '' : 's'
                } this article targets, vs the week before publish. Correlation, not proof.`}
              </p>
            </div>
          </div>

          <div className="flex flex-col">
            {data.prompts.map((entry, index) => (
              <PromptImpactRow
                key={entry.prompt_id ?? `untracked-${index}`}
                entry={entry}
                publishedWeek={data.published_week}
              />
            ))}
          </div>
        </>
      )}
    </div>
  );
};

export default PromptImpactPanel;
