import {
  customElement,
  DeesElement,
  type TemplateResult,
  html,
  property,
  css,
  cssManager,
  state,
  query,
} from '@design.estate/dees-element';
import { DeesIcon } from '@design.estate/dees-catalog';
import type {
  IDataProvider,
  IProviderMessage,
  IEcobridgeMessage,
  IProviderReadyPayload,
  IProviderResponsePayload,
  IProviderDataOfferPayload,
  IFeatureChangeRequest,
  TProviderFeature,
  TProviderStatus,
  ISendDataPayload,
  IRequestDataPayload,
} from '../interfaces/dataprovider.js';
import { demo } from './eco-provider-frame.demo.js';

// Ensure components are registered
DeesIcon;

declare global {
  interface HTMLElementTagNameMap {
    'eco-provider-frame': EcoProviderFrame;
  }
}

@customElement('eco-provider-frame')
export class EcoProviderFrame extends DeesElement {
  public static demo = demo;
  public static demoGroup = 'Elements';

  public static styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        width: 100%;
        height: 100%;
        position: relative;
      }

      .provider-frame-container {
        width: 100%;
        height: 100%;
        position: relative;
        background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 8%)')};
        border-radius: 8px;
        overflow: hidden;
      }

      iframe {
        width: 100%;
        height: 100%;
        border: none;
      }

      .loading-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 16px;
        background: ${cssManager.bdTheme('rgba(255,255,255,0.9)', 'rgba(0,0,0,0.8)')};
        color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
        z-index: 10;
      }

      .loading-overlay dees-icon {
        animation: spin 1s linear infinite;
      }

      @keyframes spin {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
      }

      .error-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 16px;
        background: ${cssManager.bdTheme('rgba(255,255,255,0.95)', 'rgba(0,0,0,0.9)')};
        color: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 70%)')};
        z-index: 10;
        padding: 32px;
        text-align: center;
      }

      .error-overlay .error-icon {
        color: hsl(0 72% 50%);
      }

      .error-title {
        font-size: 16px;
        font-weight: 600;
        color: hsl(0 72% 50%);
      }

      .error-message {
        font-size: 14px;
        max-width: 400px;
        line-height: 1.5;
      }

      .retry-button {
        margin-top: 8px;
        padding: 10px 20px;
        font-size: 14px;
        font-weight: 500;
        background: hsl(217 91% 60%);
        color: white;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        transition: background 0.15s ease;
      }

      .retry-button:hover {
        background: hsl(217 91% 55%);
      }

      .disconnected-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 12px;
        background: ${cssManager.bdTheme('rgba(255,255,255,0.95)', 'rgba(0,0,0,0.9)')};
        color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')};
        z-index: 10;
      }
    `,
  ];

  @property({ type: String })
  accessor providerId = '';

  @property({ type: String })
  accessor providerUrl = '';

  @property({ type: String })
  accessor providerName = '';

  @property({ type: Array })
  accessor confirmedFeatures: TProviderFeature[] = [];

  @property({ type: Boolean })
  accessor sandboxed = true;

  @state()
  accessor status: TProviderStatus = 'loading';

  @state()
  accessor error: string | null = null;

  @state()
  accessor currentFeatures: TProviderFeature[] = [];

  @query('iframe')
  accessor iframe: HTMLIFrameElement | null;

  private messageHandler: ((e: MessageEvent) => void) | null = null;
  private pendingRequests: Map<string, {
    resolve: (value: IProviderResponsePayload) => void;
    reject: (error: Error) => void;
    timeout: ReturnType<typeof setTimeout>;
  }> = new Map();

  async connectedCallback(): Promise<void> {
    await super.connectedCallback();
    this.setupMessageListener();
  }

  async disconnectedCallback(): Promise<void> {
    await super.disconnectedCallback();
    this.cleanupMessageListener();
    this.clearPendingRequests();
  }

  private setupMessageListener(): void {
    this.messageHandler = (event: MessageEvent) => {
      this.handleProviderMessage(event);
    };
    window.addEventListener('message', this.messageHandler);
  }

  private cleanupMessageListener(): void {
    if (this.messageHandler) {
      window.removeEventListener('message', this.messageHandler);
      this.messageHandler = null;
    }
  }

  private clearPendingRequests(): void {
    for (const [requestId, pending] of this.pendingRequests) {
      clearTimeout(pending.timeout);
      pending.reject(new Error('Provider disconnected'));
    }
    this.pendingRequests.clear();
  }

  public render(): TemplateResult {
    return html`
      <div class="provider-frame-container">
        ${this.providerUrl ? html`
          <iframe
            src=${this.providerUrl}
            sandbox=${this.sandboxed ? 'allow-scripts allow-same-origin allow-forms' : ''}
            @load=${this.handleIframeLoad}
            @error=${this.handleIframeError}
          ></iframe>
        ` : ''}
        ${this.renderOverlay()}
      </div>
    `;
  }

  private renderOverlay(): TemplateResult | null {
    switch (this.status) {
      case 'loading':
        return html`
          <div class="loading-overlay">
            <dees-icon .icon=${'lucide:loader'} .iconSize=${32}></dees-icon>
            <span>Loading provider...</span>
          </div>
        `;
      case 'error':
        return html`
          <div class="error-overlay">
            <dees-icon class="error-icon" .icon=${'lucide:alertCircle'} .iconSize=${48}></dees-icon>
            <span class="error-title">Connection Error</span>
            <span class="error-message">${this.error || 'Failed to connect to provider'}</span>
            <button class="retry-button" @click=${this.retry}>Retry</button>
          </div>
        `;
      case 'disconnected':
        return html`
          <div class="disconnected-overlay">
            <dees-icon .icon=${'lucide:unplug'} .iconSize=${32}></dees-icon>
            <span>Provider disconnected</span>
          </div>
        `;
      default:
        return null;
    }
  }

  private handleIframeLoad(): void {
    // Start waiting for provider-ready message
    // If not received within timeout, show error
    setTimeout(() => {
      if (this.status === 'loading') {
        this.status = 'error';
        this.error = 'Provider did not respond. Make sure it implements the ecobridge provider protocol.';
      }
    }, 10000); // 10 second timeout for provider to declare ready
  }

  private handleIframeError(): void {
    this.status = 'error';
    this.error = 'Failed to load provider URL';
  }

  private handleProviderMessage(event: MessageEvent): void {
    // Verify origin matches provider URL
    if (!this.providerUrl) return;

    try {
      const providerOrigin = new URL(this.providerUrl).origin;
      if (event.origin !== providerOrigin) return;
    } catch {
      return;
    }

    const message = event.data as IProviderMessage;
    if (!message || typeof message.type !== 'string') return;

    // Only accept messages from this provider
    if (message.providerId && message.providerId !== this.providerId) return;

    switch (message.type) {
      case 'provider-ready':
        this.handleProviderReady(message.payload as IProviderReadyPayload);
        break;
      case 'provider-features':
        this.handleFeaturesUpdate(message.payload as { features: TProviderFeature[] });
        break;
      case 'provider-response':
        this.handleProviderResponse(message.requestId!, message.payload as IProviderResponsePayload);
        break;
      case 'provider-error':
        this.handleProviderError(message.payload as { error: string });
        break;
      case 'data-offer':
        this.handleDataOffer(message.payload as IProviderDataOfferPayload);
        break;
    }
  }

  private handleProviderReady(payload: IProviderReadyPayload): void {
    this.currentFeatures = payload.features || [];

    // Check for feature changes
    if (this.confirmedFeatures.length > 0) {
      const added = this.currentFeatures.filter(f => !this.confirmedFeatures.includes(f));
      const removed = this.confirmedFeatures.filter(f => !this.currentFeatures.includes(f));

      if (added.length > 0 || removed.length > 0) {
        const request: IFeatureChangeRequest = {
          providerId: this.providerId,
          providerName: payload.name || this.providerName,
          addedFeatures: added,
          removedFeatures: removed,
          currentFeatures: this.currentFeatures,
        };

        this.dispatchEvent(new CustomEvent('provider-features-changed', {
          detail: { request },
          bubbles: true,
          composed: true,
        }));
      }
    }

    this.status = 'connected';

    const provider: IDataProvider = {
      id: this.providerId,
      name: payload.name || this.providerName,
      url: this.providerUrl,
      features: this.currentFeatures,
      confirmedFeatures: this.confirmedFeatures,
      icon: payload.icon,
      lastSeen: new Date(),
      status: 'connected',
      enabled: true,
    };

    this.dispatchEvent(new CustomEvent('provider-ready', {
      detail: { provider },
      bubbles: true,
      composed: true,
    }));
  }

  private handleFeaturesUpdate(payload: { features: TProviderFeature[] }): void {
    const previousFeatures = [...this.currentFeatures];
    this.currentFeatures = payload.features || [];

    const added = this.currentFeatures.filter(f => !previousFeatures.includes(f));
    const removed = previousFeatures.filter(f => !this.currentFeatures.includes(f));

    if (added.length > 0 || removed.length > 0) {
      const request: IFeatureChangeRequest = {
        providerId: this.providerId,
        providerName: this.providerName,
        addedFeatures: added,
        removedFeatures: removed,
        currentFeatures: this.currentFeatures,
      };

      this.dispatchEvent(new CustomEvent('provider-features-changed', {
        detail: { request },
        bubbles: true,
        composed: true,
      }));
    }
  }

  private handleProviderResponse(requestId: string, response: IProviderResponsePayload): void {
    const pending = this.pendingRequests.get(requestId);
    if (pending) {
      clearTimeout(pending.timeout);
      this.pendingRequests.delete(requestId);
      pending.resolve(response);
    }

    this.dispatchEvent(new CustomEvent('provider-response', {
      detail: { requestId, response },
      bubbles: true,
      composed: true,
    }));
  }

  private handleProviderError(payload: { error: string }): void {
    this.dispatchEvent(new CustomEvent('provider-error', {
      detail: { error: payload.error },
      bubbles: true,
      composed: true,
    }));
  }

  private handleDataOffer(offer: IProviderDataOfferPayload): void {
    this.dispatchEvent(new CustomEvent('provider-data-offer', {
      detail: { offer },
      bubbles: true,
      composed: true,
    }));
  }

  // Public API
  public retry(): void {
    this.status = 'loading';
    this.error = null;
    if (this.iframe) {
      this.iframe.src = this.providerUrl;
    }
  }

  public sendMessage(message: IEcobridgeMessage): void {
    if (!this.iframe?.contentWindow) {
      console.warn('Cannot send message: iframe not ready');
      return;
    }

    try {
      const origin = new URL(this.providerUrl).origin;
      this.iframe.contentWindow.postMessage(message, origin);
    } catch (e) {
      console.error('Failed to send message to provider:', e);
    }
  }

  public async sendData(data: ISendDataPayload, timeoutMs = 30000): Promise<IProviderResponsePayload> {
    const requestId = crypto.randomUUID();

    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.pendingRequests.delete(requestId);
        reject(new Error('Request timeout'));
      }, timeoutMs);

      this.pendingRequests.set(requestId, { resolve, reject, timeout });

      this.sendMessage({
        type: 'send-data',
        requestId,
        payload: data,
      });
    });
  }

  public async requestData(request: IRequestDataPayload, timeoutMs = 30000): Promise<IProviderResponsePayload> {
    const requestId = crypto.randomUUID();

    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.pendingRequests.delete(requestId);
        reject(new Error('Request timeout'));
      }, timeoutMs);

      this.pendingRequests.set(requestId, { resolve, reject, timeout });

      this.sendMessage({
        type: 'request-data',
        requestId,
        payload: request,
      });
    });
  }

  public ping(): void {
    this.sendMessage({
      type: 'ping',
      requestId: crypto.randomUUID(),
    });
  }

  public hasFeature(feature: TProviderFeature): boolean {
    return this.confirmedFeatures.includes(feature);
  }

  public getStatus(): TProviderStatus {
    return this.status;
  }
}
