import type { Workspace } from '../hooks/useTabs';
import type { MappingsUpdatedEvent } from '../types/mappings';
import type { EarningsPanelData } from '../types/financial';
import type { TerminalCommandResponse } from '../types/terminal';

export interface RefreshableEarningsPanel {
  workspaceId: string;
  panelEntryId: string;
  sourceCommand: string;
  ticker: string;
  frequency: 'annual' | 'quarterly';
  start?: string;
  end?: string;
  periodStartDate?: string;
  periodEndDate?: string;
}

interface RefreshAffectedEarningsPanelsArgs {
  workspaces: Workspace[];
  event: MappingsUpdatedEvent;
  execute: (
    panel: RefreshableEarningsPanel,
  ) => Promise<TerminalCommandResponse>;
  replacePanel: (
    panel: RefreshableEarningsPanel,
    response: TerminalCommandResponse,
  ) => void;
  reportFailure: (panel: RefreshableEarningsPanel, reason: string) => void;
  inFlightKeys: Set<string>;
}

export const getRefreshableEarningsPanel = (
  workspace: Workspace,
): RefreshableEarningsPanel | null => {
  for (let index = workspace.history.length - 1; index >= 0; index -= 1) {
    const entry = workspace.history[index];
    if (entry.type !== 'panel' || typeof entry.content === 'string') {
      continue;
    }
    if (entry.content.type !== 'earnings') {
      return null;
    }

    const sourceCommand = findSourceCommand(workspace, index);
    if (!sourceCommand) {
      return null;
    }

    const parsed = parseEarningsSourceCommand(sourceCommand);
    if (!parsed) {
      return null;
    }

    const range = derivePanelRange(entry.content.data);

    return {
      workspaceId: workspace.id,
      panelEntryId: entry.id,
      sourceCommand,
      ticker: parsed.ticker,
      frequency: parsed.frequency,
      start: parsed.start ?? entry.content.data.selectedPeriodStart ?? undefined,
      end: parsed.end ?? entry.content.data.selectedPeriodEnd ?? undefined,
      periodStartDate: range.periodStartDate,
      periodEndDate: range.periodEndDate,
    };
  }

  return null;
};

export const shouldRefreshEarningsPanel = (
  panel: RefreshableEarningsPanel,
  event: MappingsUpdatedEvent,
): boolean => {
  if (event.refreshScope.fullRefresh) {
    return true;
  }

  return event.refreshScope.affectedScopes.some((scope) => {
    if (
      scope.ticker &&
      scope.ticker.trim().toUpperCase() !== panel.ticker.toUpperCase()
    ) {
      return false;
    }

    if (!scope.periodStart && !scope.periodEnd) {
      return true;
    }

    return scopeOverlapsPanel(panel, scope.periodStart, scope.periodEnd);
  });
};

export const refreshPanelKey = (panel: RefreshableEarningsPanel): string =>
  `${panel.workspaceId}:${panel.sourceCommand}`;

export const refreshAffectedEarningsPanels = async ({
  workspaces,
  event,
  execute,
  replacePanel,
  reportFailure,
  inFlightKeys,
}: RefreshAffectedEarningsPanelsArgs): Promise<void> => {
  const panels = workspaces
    .map(getRefreshableEarningsPanel)
    .filter((panel): panel is RefreshableEarningsPanel => panel !== null)
    .filter((panel) => shouldRefreshEarningsPanel(panel, event));

  await Promise.all(
    panels.map(async (panel) => {
      const key = refreshPanelKey(panel);
      if (inFlightKeys.has(key)) {
        return;
      }

      inFlightKeys.add(key);
      try {
        const response = await execute(panel);
        if (response.kind === 'panel' && response.panel.type === 'earnings') {
          replacePanel(panel, response);
          return;
        }

        reportFailure(
          panel,
          'Failed to refresh earnings after mapping update.',
        );
      } catch (error) {
        reportFailure(
          panel,
          error instanceof Error
            ? `Failed to refresh earnings after mapping update: ${error.message}`
            : 'Failed to refresh earnings after mapping update.',
        );
      } finally {
        inFlightKeys.delete(key);
      }
    }),
  );
};

const parseEarningsSourceCommand = (
  command: string,
):
  | {
      ticker: string;
      frequency: 'annual' | 'quarterly';
      start?: string;
      end?: string;
    }
  | null => {
  const parts = command.trim().split(/\s+/);
  if (parts[0] !== '/em' || !parts[1]) {
    return null;
  }

  if (parts[2] && parts[2] !== 'annual' && parts[2] !== 'quarterly') {
    return null;
  }

  return {
    ticker: parts[1].toUpperCase(),
    frequency: (parts[2] as 'annual' | 'quarterly' | undefined) ?? 'annual',
    start: parts[3],
    end: parts[4],
  };
};

const findSourceCommand = (
  workspace: Workspace,
  panelIndex: number,
): string | null => {
  for (let index = panelIndex - 1; index >= 0; index -= 1) {
    const candidate = workspace.history[index];
    if (
      candidate.type === 'command' &&
      typeof candidate.content === 'string' &&
      candidate.content.startsWith('/')
    ) {
      return candidate.content;
    }
  }

  return null;
};

const derivePanelRange = (
  data: EarningsPanelData,
): { periodStartDate?: string; periodEndDate?: string } => {
  const periodEnds = data.periods
    .map((period) => period.periodEnd)
    .filter((value): value is string => Boolean(value))
    .sort();

  return {
    periodStartDate: periodEnds[0],
    periodEndDate: periodEnds[periodEnds.length - 1],
  };
};

const scopeOverlapsPanel = (
  panel: RefreshableEarningsPanel,
  periodStart?: string,
  periodEnd?: string,
): boolean => {
  if (!panel.periodStartDate || !panel.periodEndDate) {
    return true;
  }

  const panelStart = parseIsoDate(panel.periodStartDate);
  const panelEnd = parseIsoDate(panel.periodEndDate);
  const scopeStart = periodStart ? parseIsoDate(periodStart) : null;
  const scopeEnd = periodEnd ? parseIsoDate(periodEnd) : null;

  if (
    panelStart === null ||
    panelEnd === null ||
    (periodStart && scopeStart === null) ||
    (periodEnd && scopeEnd === null)
  ) {
    return true;
  }

  const effectiveScopeStart = scopeStart ?? Number.NEGATIVE_INFINITY;
  const effectiveScopeEnd = scopeEnd ?? Number.POSITIVE_INFINITY;

  return effectiveScopeStart <= panelEnd && panelStart <= effectiveScopeEnd;
};

const parseIsoDate = (value: string): number | null => {
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? null : parsed;
};
