/**
 * softInputRuntime —— 用例页一键执行的运行时(信号总线 + 帧采样 + 真实弹收周期)。
 *
 * 信号来源二选一(按用例页的避让承担方):
 *   view   —— <AvoidSoftInputView> 的 4 个组件事件(onSoftInputShown / onSoftInputHidden /
 *             onSoftInputHeightChange / onSoftInputAppliedOffsetChange);
 *   module —— TurboModule 的 4 个 device 事件(经 AvoidSoftInput.onXxx 订阅)。
 * 两路信号总是同时接入(供 F04-002 等两侧互证),cycle() 只按 primary 判定弹收进度。
 *
 * 真实性:focus() 经 TextInput.focus() 真实唤起系统软键盘,dismiss() 经 Keyboard.dismiss() 真实收起;
 * 所有读数只来自库抛出的事件流(appliedOffset 逐帧 / shown / hidden / heightChange 载荷),页面不自算位移。
 */
import { Keyboard } from 'react-native';
import type { TextInput } from 'react-native';
import { AvoidSoftInput } from '@hxa-rn/react-native-avoid-softinput';

export type SignalSource = 'view' | 'module';
export type SignalKind = 'shown' | 'hidden' | 'heightChange' | 'appliedOffset';

export interface Signal {
  source: SignalSource;
  kind: SignalKind;
  value: number;
  t: number;
}

export interface SourceCounters {
  shown: number;
  hidden: number;
  heightChange: number;
  appliedOffset: number;
  lastShownHeight: number;
  lastHiddenHeight: number;
  lastHeight: number;
  lastAppliedOffset: number;
}

export interface PhaseMetrics {
  /** 本阶段(show/hide)appliedOffset 帧数(含首帧 0 与末帧终值) */
  frames: number;
  /** 本阶段峰值(绝对值) */
  peak: number;
  /** 阶段末值(最后一帧 appliedOffset) */
  last: number;
  /** 信号(shown/hidden)到首个「值变化」帧的间隔 ms;无变化帧 = -1 */
  delayMs: number;
  /** 首个变化帧到末帧的间隔 ms;无变化帧 = -1 */
  durationMs: number;
  /** 动画时间过 1/4 时的位移完成百分比(0-100);无变化帧 = -1 */
  quarterRatio: number;
  /** 动画时间过半时的位移完成百分比(0-100);无变化帧 = -1 */
  midRatio: number;
  /** 本阶段 shown/hidden 载荷 softInputHeight(未收到 = -1) */
  signalHeight: number;
}

export interface CycleResult {
  show: PhaseMetrics;
  hide: PhaseMetrics;
}

export interface CycleOptions {
  /** 弹出阶段预计动画耗时(delay+duration),默认 660 */
  showBudgetMs?: number;
  /** 收起阶段预计动画耗时(delay+duration),默认 220 */
  hideBudgetMs?: number;
  /** 弹出到位后的回调(在收起之前执行,可用于中途改参数 / 抓拍状态) */
  onShown?: (show: PhaseMetrics) => void | Promise<void>;
  /** true = 弹出到位后不收起,直接返回(hide 为空指标) */
  keepShown?: boolean;
}

interface Sample {
  t: number;
  offset: number;
}

const SIGNAL_TIMEOUT_MS = 8000;
const SETTLE_IDLE_MS = 450;
const SETTLE_EXTRA_MS = 400;
const SETTLE_CAP_MS = 4000;

function emptyCounters(): SourceCounters {
  return {
    shown: 0,
    hidden: 0,
    heightChange: 0,
    appliedOffset: 0,
    lastShownHeight: -1,
    lastHiddenHeight: -1,
    lastHeight: -1,
    lastAppliedOffset: 0,
  };
}

function emptyMetrics(): PhaseMetrics {
  return {
    frames: 0,
    peak: 0,
    last: 0,
    delayMs: -1,
    durationMs: -1,
    quarterRatio: -1,
    midRatio: -1,
    signalHeight: -1,
  };
}

export function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export class SoftInputRuntime {
  public readonly counters: Record<SignalSource, SourceCounters> = {
    view: emptyCounters(),
    module: emptyCounters(),
  };

  private primary: SignalSource;
  private inputRef: { current: TextInput | null };
  private listeners: Array<(s: Signal) => void> = [];
  private deviceSubs: Array<{ remove: () => void }> = [];
  private samples: Sample[] = [];
  private recording = false;
  private lastSampleT = 0;

  constructor(primary: SignalSource, inputRef: { current: TextInput | null }) {
    this.primary = primary;
    this.inputRef = inputRef;
  }

  /** 接入 device 事件(module 侧信号);返回解绑函数 */
  attachDeviceEvents(): () => void {
    const subs = [
      AvoidSoftInput.onSoftInputShown(({ softInputHeight }) => this.emit('module', 'shown', softInputHeight)),
      AvoidSoftInput.onSoftInputHidden(({ softInputHeight }) => this.emit('module', 'hidden', softInputHeight)),
      AvoidSoftInput.onSoftInputHeightChange(({ softInputHeight }) => this.emit('module', 'heightChange', softInputHeight)),
      AvoidSoftInput.onSoftInputAppliedOffsetChange(({ appliedOffset }) => this.emit('module', 'appliedOffset', appliedOffset)),
    ];
    this.deviceSubs = subs;
    return () => {
      subs.forEach((s) => s.remove());
      this.deviceSubs = [];
    };
  }

  /** 组件事件入口(CaseHarness 在 <AvoidSoftInputView> 回调里调用) */
  emit(source: SignalSource, kind: SignalKind, value: number): void {
    const t = Date.now();
    const c = this.counters[source];
    if (kind === 'shown') {
      c.shown += 1;
      c.lastShownHeight = value;
      c.lastHeight = value;
    } else if (kind === 'hidden') {
      c.hidden += 1;
      c.lastHiddenHeight = value;
      c.lastHeight = value;
    } else if (kind === 'heightChange') {
      c.heightChange += 1;
      c.lastHeight = value;
    } else {
      c.appliedOffset += 1;
      c.lastAppliedOffset = value;
      if (source === this.primary && this.recording) {
        this.samples.push({ t, offset: value });
        this.lastSampleT = t;
      }
    }
    const sig: Signal = { source, kind, value, t };
    this.listeners.slice().forEach((fn) => fn(sig));
  }

  subscribe(fn: (s: Signal) => void): () => void {
    this.listeners.push(fn);
    return () => {
      this.listeners = this.listeners.filter((x) => x !== fn);
    };
  }

  resetCounters(): void {
    this.counters.view = emptyCounters();
    this.counters.module = emptyCounters();
  }

  get primarySource(): SignalSource {
    return this.primary;
  }

  /** 当前 primary 侧最近一帧 appliedOffset(= 当前施加的偏移 vp) */
  get currentOffset(): number {
    return this.counters[this.primary].lastAppliedOffset;
  }

  focus(): boolean {
    const node = this.inputRef.current;
    if (!node) {
      return false;
    }
    node.focus();
    return true;
  }

  dismiss(): void {
    Keyboard.dismiss();
    const node = this.inputRef.current;
    if (node && node.isFocused && node.isFocused()) {
      node.blur();
    }
  }

  /** 等 primary 侧某信号;超时抛错 */
  waitSignal(kind: SignalKind, timeoutMs: number = SIGNAL_TIMEOUT_MS): Promise<Signal> {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        off();
        reject(new Error(`等待 ${this.primary} 侧 ${kind} 信号超时(${timeoutMs}ms)`));
      }, timeoutMs);
      const off = this.subscribe((s) => {
        if (s.source === this.primary && s.kind === kind) {
          clearTimeout(timer);
          off();
          resolve(s);
        }
      });
    });
  }

  /**
   * 等偏移帧流静默:从 sinceT 起至少过 budgetMs+SETTLE_EXTRA_MS,且最后一帧后静默 SETTLE_IDLE_MS;上限 budget+cap。
   */
  async waitSettle(sinceT: number, budgetMs: number): Promise<void> {
    const minEnd = sinceT + budgetMs + SETTLE_EXTRA_MS;
    const cap = sinceT + budgetMs + SETTLE_CAP_MS;
    for (;;) {
      const now = Date.now();
      if (now >= cap) {
        return;
      }
      if (now >= minEnd && now - Math.max(this.lastSampleT, sinceT) >= SETTLE_IDLE_MS) {
        return;
      }
      await sleep(60);
    }
  }

  private beginRecording(): void {
    this.samples = [];
    this.recording = true;
    this.lastSampleT = 0;
  }

  private endRecording(signalT: number, signalHeight: number, baseline: number): PhaseMetrics {
    this.recording = false;
    const samples = this.samples;
    this.samples = [];
    const m = emptyMetrics();
    m.frames = samples.length;
    m.signalHeight = signalHeight;
    if (samples.length === 0) {
      m.last = baseline;
      return m;
    }
    m.last = samples[samples.length - 1].offset;
    for (let i = 0; i < samples.length; i++) {
      const abs = Math.abs(samples[i].offset);
      if (abs > m.peak) {
        m.peak = abs;
      }
    }
    // 首个「值变化」帧:跳过 onBeforeStart 抛出的与基线相同的帧
    let firstIdx = -1;
    for (let i = 0; i < samples.length; i++) {
      if (samples[i].offset !== baseline) {
        firstIdx = i;
        break;
      }
    }
    if (firstIdx < 0) {
      return m;
    }
    const first = samples[firstIdx];
    const last = samples[samples.length - 1];
    m.delayMs = Math.max(0, Math.round(first.t - signalT));
    m.durationMs = Math.round(last.t - first.t);
    const span = last.offset - baseline;
    // 动画起点:库在 animator.start() 里先抛一帧基线值(onBeforeStart),delay=0 时它就是起点;
    // 缓入类曲线头几帧 trunc 后仍等于基线,若以「首个变化帧」为起点会把四分位/中点系统性抬高 3–5 个百分点。
    // 首帧到首个变化帧相隔过久(带 delay 的动画)时退回「首个变化帧前推两帧」。
    const startT: number = first.t - samples[0].t <= 150 ? samples[0].t : first.t - 33;
    const ratioAt = (fraction: number): number => {
      if (span === 0) {
        return -1;
      }
      const target = startT + (last.t - startT) * fraction;
      // 在相邻两帧之间线性插值取该时刻的位移(比取最近帧少半帧误差)
      let prev: Sample = samples[0];
      for (let i = 0; i < samples.length; i++) {
        const s = samples[i];
        if (s.t >= target) {
          if (s.t === prev.t) {
            return Math.round(((s.offset - baseline) / span) * 100);
          }
          const k = (target - prev.t) / (s.t - prev.t);
          const v = prev.offset + (s.offset - prev.offset) * k;
          return Math.round(((v - baseline) / span) * 100);
        }
        prev = s;
      }
      return Math.round(((last.offset - baseline) / span) * 100);
    };
    m.quarterRatio = ratioAt(0.25);
    m.midRatio = ratioAt(0.5);
    return m;
  }

  /** 一轮真实弹收:focus → shown → 到位 → [onShown] → dismiss → hidden → 复位 */
  async cycle(opts: CycleOptions = {}): Promise<CycleResult> {
    const showBudget = opts.showBudgetMs ?? 660;
    const hideBudget = opts.hideBudgetMs ?? 220;
    const baseline = this.currentOffset;
    this.beginRecording();
    if (!this.focus()) {
      this.recording = false;
      throw new Error('底部输入框未挂载,无法程序化聚焦');
    }
    const shownSig = await this.waitSignal('shown');
    await this.waitSettle(shownSig.t, showBudget);
    const show = this.endRecording(shownSig.t, shownSig.value, baseline);
    if (opts.onShown) {
      await opts.onShown(show);
    }
    if (opts.keepShown) {
      return { show, hide: emptyMetrics() };
    }
    const hide = await this.dismissAndSettle(hideBudget);
    return { show, hide };
  }

  /** 从弹出态收起并等复位结束(供两阶段用例的 hidden 监听或 cycle 内部使用) */
  async dismissAndSettle(hideBudgetMs: number = 220): Promise<PhaseMetrics> {
    const baseline = this.currentOffset;
    this.beginRecording();
    this.dismiss();
    const hiddenSig = await this.waitSignal('hidden');
    await this.waitSettle(hiddenSig.t, hideBudgetMs);
    return this.endRecording(hiddenSig.t, hiddenSig.value, baseline);
  }

  /** 两阶段用例:不主动收起,等外部(系统返回键)收起后结算 hide 阶段 */
  async awaitExternalHide(hideBudgetMs: number = 220, timeoutMs: number = 120000): Promise<PhaseMetrics> {
    const baseline = this.currentOffset;
    this.beginRecording();
    const hiddenSig = await this.waitSignal('hidden', timeoutMs);
    await this.waitSettle(hiddenSig.t, hideBudgetMs);
    return this.endRecording(hiddenSig.t, hiddenSig.value, baseline);
  }

  dispose(): void {
    this.deviceSubs.forEach((s) => s.remove());
    this.deviceSubs = [];
    this.listeners = [];
    this.recording = false;
  }
}

/** 常用格式化 */
export function fmtRatio(m: PhaseMetrics): string {
  if (m.quarterRatio < 0 || m.midRatio < 0) {
    return '未采到';
  }
  return `${m.quarterRatio}%/${m.midRatio}%`;
}

export function fmtTiming(m: PhaseMetrics): string {
  if (m.delayMs < 0) {
    return '未采到';
  }
  return `${m.delayMs}/${m.durationMs}`;
}

export function within(actual: number, expected: number, tol: number): boolean {
  return actual >= 0 && Math.abs(actual - expected) <= tol;
}

/** 逐条判定:全部为真 = PASS;否则 reason 列出未达项 */
export function judge(checks: Array<[string, boolean]>): { pass: boolean; reason?: string } {
  const failed = checks.filter((c) => !c[1]).map((c) => c[0]);
  if (failed.length === 0) {
    return { pass: true };
  }
  return { pass: false, reason: `未达预期:${failed.join(';')}` };
}

export function errorText(e: unknown): string {
  if (e instanceof Error) {
    return `error: [${e.name}] ${e.message}`;
  }
  return `error: [unknown] ${String(e)}`;
}