import { Injectable, computed, inject, signal } from '@angular/core';
import { Result } from '@shared/kernel/fp';
import { RemoteData } from '@shared/application/remote-data';
import { createStore } from '@shared/application/store';
import {
  Brief,
  allDiagnostics,
  canSubmit,
  hasBlockingErrors,
  unresolvedPlaceholders,
} from '@brief/domain/brief';
import { BriefMsg, BriefState, initial, reduce } from '@brief/domain/brief.machine';
import { BriefAdapter, BriefView } from '@brief/infrastructure/brief.adapter';

/** Transient action state (submit/approve/reject/send/resetDemo) — one tagged union
    instead of a busy boolean + a nullable error sitting side by side. */
type ActionState = { tag: 'Idle' } | { tag: 'Busy' } | { tag: 'Failed'; error: string };

/** Debounced-autosave indicator, shown in a small status line near the toolbar —
    a separate concern from ActionState (a stale autosave error doesn't block
    submit/approve/reject), but tag-aligned with it for one consistent idiom. */
type SaveState = { tag: 'Idle' } | { tag: 'Saving' } | { tag: 'Saved' } | { tag: 'Error' };

type LoadedBriefState = Extract<BriefState, { tag: 'loaded' }>;

/**
 * Root singleton for the letter: the Elm store (Model + dispatch), the derived
 * read-model, and the commands (effects) that call the adapter and dispatch the
 * outcome. Mirrors `BigProfileStore`. All of `canEdit`/`canApprove`/`canReject`/
 * `canSend`, `diagnostics`, `unresolved`, `canSubmit` are DERIVED here — never
 * stored. The permission flags come from the server's decision DTO (PRD-0002 phase
 * P1) via `BriefState.loaded.decisions` — this store never computes them itself.
 */
@Injectable({ providedIn: 'root' })
export class BriefStore {
  private adapter = inject(BriefAdapter);
  private store = createStore<BriefState, BriefMsg>(initial, reduce);

  readonly model = this.store.model;

  private actionState = signal<ActionState>({ tag: 'Idle' });
  readonly busy = computed(() => this.actionState().tag === 'Busy');
  readonly lastError = computed(() => {
    const s = this.actionState();
    return s.tag === 'Failed' ? s.error : null;
  });

  /** Surfaced autosave state for the indicator + aria-live region. */
  readonly saveState = signal<SaveState>({ tag: 'Idle' });

  /** The load lifecycle as `RemoteData`, for `<app-async>` — the machine keeps
      owning the letter's own domain lifecycle (draft/submitted/approved/…); this is
      purely a projection of its loading/failed tags onto the shared async seam. */
  readonly remoteData = computed<RemoteData<Error | undefined, LoadedBriefState>>(() => {
    const s = this.model();
    switch (s.tag) {
      case 'loading':
        return { tag: 'Loading' };
      case 'failed':
        return { tag: 'Failure', error: new Error(s.reason) };
      case 'loaded':
        return { tag: 'Success', value: s };
    }
  });

  private brief = computed<Brief | null>(() => {
    const s = this.model();
    return s.tag === 'loaded' ? s.brief : null;
  });

  readonly canEdit = computed(() => this.decisions()?.canEdit ?? false);
  readonly canApprove = computed(() => this.decisions()?.canApprove ?? false);
  readonly canReject = computed(() => this.decisions()?.canReject ?? false);
  readonly canSend = computed(() => this.decisions()?.canSend ?? false);

  private decisions = computed(() => {
    const s = this.model();
    return s.tag === 'loaded' ? s.decisions : null;
  });
  readonly diagnostics = computed(() => (this.brief() ? allDiagnostics(this.brief()!) : []));
  readonly unresolved = computed(() => (this.brief() ? unresolvedPlaceholders(this.brief()!) : []));
  /** Submit is allowed only when required sections are filled AND no blocking errors. */
  readonly canSubmit = computed(() => {
    const b = this.brief();
    return !!b && canSubmit(b) && !hasBlockingErrors(this.diagnostics());
  });

  async load() {
    const r = await this.adapter.load();
    if (r.ok) this.store.dispatch({ tag: 'BriefLoaded', ...r.value });
    else this.store.dispatch({ tag: 'BriefLoadFailed', reason: r.error });
  }

  /** An edit: apply it optimistically in the pure reducer, then debounce-save. */
  edit(msg: BriefMsg) {
    this.store.dispatch(msg);
    this.scheduleSave();
  }

  private saveTimer?: ReturnType<typeof setTimeout>;
  private scheduleSave() {
    if (!this.canEdit()) return;
    clearTimeout(this.saveTimer);
    // ponytail: 600ms debounce like the wizard draft-sync; the server is the store of record.
    this.saveTimer = setTimeout(() => void this.flushSave(), 600);
  }
  private async flushSave() {
    const b = this.brief();
    if (!b) return;
    this.saveState.set({ tag: 'Saving' });
    const r = await this.adapter.save(b.sections);
    if (r.ok) {
      this.saveState.set({ tag: 'Saved' });
    } else {
      this.actionState.set({ tag: 'Failed', error: r.error });
      this.saveState.set({ tag: 'Error' });
    }
  }

  /** Demo "start over": recreate the brief server-side and load the fresh view. */
  async resetDemo() {
    this.actionState.set({ tag: 'Busy' });
    clearTimeout(this.saveTimer);
    const r = await this.adapter.reset();
    this.saveState.set({ tag: 'Idle' });
    if (r.ok) {
      this.actionState.set({ tag: 'Idle' });
      this.store.dispatch({ tag: 'BriefLoaded', ...r.value });
    } else {
      this.actionState.set({ tag: 'Failed', error: r.error });
    }
  }

  submit = () => this.transition(() => this.adapter.submit());
  approve = () => this.transition(() => this.adapter.approve());
  reject = (comments: string) => this.transition(() => this.adapter.reject(comments));
  send = () => this.transition(() => this.adapter.send());

  // A transition: flush any pending save, call the server (authoritative), then mirror
  // the returned status through the pure reducer's guarded transition.
  private async transition(action: () => Promise<Result<string, BriefView>>) {
    this.actionState.set({ tag: 'Busy' });
    clearTimeout(this.saveTimer);
    await this.flushSave();
    const r = await action();
    if (!r.ok) {
      this.actionState.set({ tag: 'Failed', error: r.error });
      return;
    }
    this.actionState.set({ tag: 'Idle' });
    this.applyServerStatus(r.value);
  }

  private applyServerStatus(view: BriefView) {
    const { brief, decisions } = view;
    const s = brief.status;
    switch (s.tag) {
      case 'submitted':
        this.store.dispatch({ tag: 'Submitted', by: s.submittedBy, at: s.submittedAt, decisions });
        break;
      case 'approved':
        this.store.dispatch({ tag: 'Approved', by: s.approvedBy, at: s.approvedAt, decisions });
        break;
      case 'rejected':
        this.store.dispatch({
          tag: 'Rejected',
          by: s.rejectedBy,
          at: s.rejectedAt,
          comments: s.comments,
          decisions,
        });
        break;
      case 'sent':
        this.store.dispatch({ tag: 'Sent', at: s.sentAt, decisions });
        break;
      case 'draft':
        // reopened by a save on a rejected letter — reducer already handled it locally.
        break;
    }
  }
}
