import { Task } from "@lit/task";
import debug from "debug";
import { css, html, type PropertyValueMap } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";

import { DelayedLoadingState } from "../DelayedLoadingState.js";
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
import { TWMixin } from "../gui/TWMixin.js";
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts";
import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.ts";
import { makeVideoSeekTask } from "./EFMedia/videoTasks/makeVideoSeekTask.ts";
import { makeVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoSegmentFetchTask.ts";
import { makeVideoSegmentIdTask } from "./EFMedia/videoTasks/makeVideoSegmentIdTask.ts";
import { EFMedia } from "./EFMedia.js";

// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
declare global {
  var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
}

const log = debug("ef:elements:EFVideo");

interface LoadingState {
  isLoading: boolean;
  operation: "scrub-segment" | "video-segment" | "seeking" | "decoding" | null;
  message: string;
}

@customElement("ef-video")
export class EFVideo extends TWMixin(EFMedia) {
  static styles = [
    css`
      :host {
        display: block;
        position: relative;
      }
      canvas {
        all: inherit;
        overflow: hidden;
        position: static;
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
        border: none;
        outline: none;
        box-shadow: none;
      }
      .loading-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.6);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 10;
        backdrop-filter: blur(2px);
      }
      .loading-content {
        background: rgba(0, 0, 0, 0.8);
        border-radius: 8px;
        padding: 16px 24px;
        display: flex;
        align-items: center;
        gap: 12px;
        color: white;
        font-size: 14px;
        font-weight: 500;
      }
      .loading-spinner {
        width: 20px;
        height: 20px;
        border: 2px solid rgba(255, 255, 255, 0.2);
        border-left: 2px solid #fff;
        border-radius: 50%;
        animation: spin 1s linear infinite;
      }
      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }
      .loading-message {
        font-size: 12px;
        opacity: 0.8;
      }
    `,
  ];
  canvasRef = createRef<HTMLCanvasElement>();

  /**
   * Duration in milliseconds for video buffering ahead of current time
   * @domAttribute "video-buffer-duration"
   */
  @property({ type: Number, attribute: "video-buffer-duration" })
  videoBufferDurationMs = 60000; // 60 seconds

  /**
   * Maximum number of concurrent video segment fetches for buffering
   * @domAttribute "max-video-buffer-fetches"
   */
  @property({ type: Number, attribute: "max-video-buffer-fetches" })
  maxVideoBufferFetches = 2;

  /**
   * Enable/disable video buffering system
   * @domAttribute "enable-video-buffering"
   */
  @property({ type: Boolean, attribute: "enable-video-buffering" })
  enableVideoBuffering = true;

  // Video-specific tasks
  videoSegmentIdTask = makeVideoSegmentIdTask(this);
  videoInitSegmentFetchTask = makeVideoInitSegmentFetchTask(this);
  videoSegmentFetchTask = makeVideoSegmentFetchTask(this);
  videoInputTask = makeVideoInputTask(this);
  videoSeekTask = makeVideoSeekTask(this);
  videoBufferTask = makeVideoBufferTask(this);

  /**
   * Delayed loading state manager for user feedback
   */
  private delayedLoadingState: DelayedLoadingState;

  /**
   * Loading state for user feedback
   */
  @state()
  loadingState = {
    isLoading: false,
    operation: null as LoadingState["operation"],
    message: "",
  };

  constructor() {
    super();

    // Initialize delayed loading state with callback to update UI
    this.delayedLoadingState = new DelayedLoadingState(
      250,
      (isLoading, message) => {
        this.setLoadingState(isLoading, null, message);
      },
    );
  }

  render() {
    return html`
      <canvas ${ref(this.canvasRef)}></canvas>
      ${
        this.loadingState.isLoading
          ? html`
        <div class="loading-overlay">
          <div class="loading-content">
            <div class="loading-spinner"></div>
            <div>
              <div>Loading Video...</div>
              <div class="loading-message">${this.loadingState.message}</div>
            </div>
          </div>
        </div>
      `
          : ""
      }
    `;
  }

  get canvasElement() {
    const referencedCanvas = this.canvasRef.value;
    if (referencedCanvas) {
      return referencedCanvas;
    }
    const shadowCanvas = this.shadowRoot?.querySelector("canvas");
    if (shadowCanvas) {
      return shadowCanvas;
    }
    return undefined;
  }

  frameTask = new Task(this, {
    autoRun: EF_INTERACTIVE,
    args: () => [this.desiredSeekTimeMs] as const,
    onError: (error) => {
      console.error("frameTask error", error);
    },
    onComplete: () => {},
    task: async ([_desiredSeekTimeMs], { signal }) => {
      await this.videoSeekTask.taskComplete;
      await this.paintTask.taskComplete;
      if (signal.aborted) {
        return;
      }
    },
  });

  protected updated(
    changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
  ): void {
    super.updated(changedProperties);
  }

  /**
   * Start a delayed loading operation for testing
   */
  startDelayedLoading(
    operationId: string,
    message: string,
    options: { background?: boolean } = {},
  ): void {
    this.delayedLoadingState.startLoading(operationId, message, options);
  }

  /**
   * Clear a delayed loading operation for testing
   */
  clearDelayedLoading(operationId: string): void {
    this.delayedLoadingState.clearLoading(operationId);
  }

  /**
   * Set loading state for user feedback
   */
  private setLoadingState(
    isLoading: boolean,
    operation: LoadingState["operation"] = null,
    message = "",
  ): void {
    this.loadingState = {
      isLoading,
      operation,
      message,
    };
  }

  paintTask = new Task(this, {
    args: () => [this.desiredSeekTimeMs] as const,
    onError: (error) => {
      console.error("paintTask error", error);
    },
    onComplete: () => {},
    task: async ([_seekToMs], { signal }) => {
      await this.videoSeekTask.taskComplete;
      // Check if we're in production rendering mode vs preview mode
      const isProductionRendering = this.isInProductionRenderingMode();

      const sample = this.videoSeekTask.value;
      if (sample) {
        const videoFrame = sample.toVideoFrame();
        try {
          this.displayFrame(videoFrame, _seekToMs);
        } finally {
          videoFrame.close();
        }
      }

      // EF_FRAMEGEN-aware rendering mode detection
      if (!isProductionRendering) {
        // Preview mode: skip rendering during initialization to prevent artifacts
        if (
          !this.rootTimegroup ||
          (this.rootTimegroup.currentTimeMs === 0 &&
            this.desiredSeekTimeMs === 0)
        ) {
          return; // Skip initialization frame in preview mode
        }
        // Preview mode: proceed with rendering
      } else {
        // Production rendering mode: only render when EF_FRAMEGEN has explicitly started frame rendering
        // This prevents initialization frames before the actual render sequence begins
        if (!this.rootTimegroup) {
          return;
        }

        if (!this.isFrameRenderingActive()) {
          return; // Wait for EF_FRAMEGEN to start frame sequence
        }

        // Production mode: EF_FRAMEGEN has started frame sequence, proceed with rendering
      }

      if (signal.aborted) {
        return;
      }
    },
  });

  /**
   * Display a video frame on the canvas
   */
  private displayFrame(frame: VideoFrame, seekToMs: number): number {
    log("trace: displayFrame start", { seekToMs, frameFormat: frame.format });
    if (!this.canvasElement) {
      log("trace: displayFrame aborted - no canvas element");
      throw new Error(
        `Frame display failed: Canvas element is not available at time ${seekToMs}ms. The video component may not be properly initialized.`,
      );
    }

    const ctx = this.canvasElement.getContext("2d");
    if (!ctx) {
      log("trace: displayFrame aborted - no canvas context");
      throw new Error(
        `Frame display failed: Unable to get 2D canvas context at time ${seekToMs}ms. This may indicate a browser compatibility issue or canvas corruption.`,
      );
    }

    if (frame?.codedWidth && frame?.codedHeight) {
      if (
        this.canvasElement.width !== frame.codedWidth ||
        this.canvasElement.height !== frame.codedHeight
      ) {
        log("trace: updating canvas dimensions", {
          width: frame.codedWidth,
          height: frame.codedHeight,
        });
        this.canvasElement.width = frame.codedWidth;
        this.canvasElement.height = frame.codedHeight;
      }
    }

    if (frame.format === null) {
      log("trace: displayFrame aborted - null frame format");
      throw new Error(
        `Frame display failed: Video frame has null format at time ${seekToMs}ms. This indicates corrupted or incompatible video data.`,
      );
    }

    log("trace: drawing frame to canvas", frame.timestamp / 1000);
    ctx.drawImage(
      frame,
      0,
      0,
      this.canvasElement.width,
      this.canvasElement.height,
    );
    log("trace: frame drawn to canvas", { seekToMs });

    return seekToMs;
  }

  /**
   * Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode
   */
  private isInProductionRenderingMode(): boolean {
    // Check if EF_RENDERING function exists and returns true (production rendering)
    if (typeof window.EF_RENDERING === "function") {
      return window.EF_RENDERING();
    }

    // Check if workbench is in rendering mode
    const workbench = document.querySelector("ef-workbench") as any;
    if (workbench?.rendering) {
      return true;
    }

    // Check if EF_FRAMEGEN exists and has render options (indicates active rendering)
    if (window.EF_FRAMEGEN?.renderOptions) {
      return true;
    }

    // Default to preview mode
    return false;
  }

  /**
   * Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)
   */
  private isFrameRenderingActive(): boolean {
    if (!window.EF_FRAMEGEN?.renderOptions) {
      return false;
    }

    // In production mode, only render when EF_FRAMEGEN has actually begun frame sequence
    // Check if we're past the initialization phase by looking for explicit frame control
    const renderOptions = window.EF_FRAMEGEN.renderOptions;
    const renderStartTime = renderOptions.encoderOptions.fromMs;
    const currentTime = this.rootTimegroup?.currentTimeMs || 0;

    // We're in active frame rendering if:
    // 1. currentTime >= renderStartTime (includes the starting frame)
    return currentTime >= renderStartTime;
  }

  /**
   * Legacy getter for fragment index task (maps to videoSegmentIdTask)
   * Still used by EFCaptions
   */
  get fragmentIndexTask() {
    return this.videoSegmentIdTask;
  }

  /**
   * Clean up resources when component is disconnected
   */
  disconnectedCallback(): void {
    super.disconnectedCallback();

    // Clean up delayed loading state
    this.delayedLoadingState.clearAllLoading();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ef-video": EFVideo;
  }
}
