import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation, forwardRef, signal, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export type ColorPickerSize = 'sm' | 'md' | 'lg';
export type ColorPickerVariant = 'primary' | 'secondary' | 'success' | 'danger';
export type ColorPickerState = 'default' | 'error' | 'disabled';

interface HSL {
  h: number; // 0-360
  s: number; // 0-100
  l: number; // 0-100
}

interface RGB {
  r: number; // 0-255
  g: number; // 0-255
  b: number; // 0-255
}

@Component({
  selector: 'ui-color-picker',
  standalone: true,
  imports: [CommonModule, FormsModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ColorPickerComponent),
      multi: true
    }
  ],
  template: `
    <div 
      class="ui-color-picker"
      [class]="getWrapperClasses()"
      [attr.aria-disabled]="disabled">
      
      @if (label) {
        <label class="ui-color-picker__label" [for]="pickerId">
          {{ label }}
          @if (required) {
            <span class="ui-color-picker__required">*</span>
          }
        </label>
      }
      
      <div class="ui-color-picker__container">
        <!-- Color Wheel -->
        <div class="ui-color-picker__wheel-container">
          <canvas 
            #colorWheel
            class="ui-color-picker__wheel"
            [width]="wheelSize()"
            [height]="wheelSize()"
            [attr.tabindex]="disabled ? -1 : 0"
            [attr.role]="'slider'"
            [attr.aria-label]="'Color saturation and lightness picker'"
            [attr.aria-valuetext]="getWheelAriaText()"
            (mousedown)="startWheelDrag($event)"
            (keydown)="handleWheelKeydown($event)">
          </canvas>
          <div 
            class="ui-color-picker__wheel-indicator"
            [style.left.px]="wheelIndicatorPosition().x"
            [style.top.px]="wheelIndicatorPosition().y"
            [attr.aria-hidden]="true">
          </div>
        </div>
        
        <!-- Hue Bar -->
        <div class="ui-color-picker__hue-container">
          <canvas 
            #hueBar
            class="ui-color-picker__hue-bar"
            [width]="hueBarWidth()"
            [height]="hueBarHeight()"
            [attr.tabindex]="disabled ? -1 : 0"
            [attr.role]="'slider'"
            [attr.aria-label]="'Hue picker'"
            [attr.aria-valuemin]="0"
            [attr.aria-valuemax]="360"
            [attr.aria-valuenow]="currentColor().h"
            (mousedown)="startHueDrag($event)"
            (keydown)="handleHueKeydown($event)">
          </canvas>
          <div 
            class="ui-color-picker__hue-indicator"
            [style.left.px]="hueIndicatorPosition()"
            [attr.aria-hidden]="true">
          </div>
        </div>
        
        <!-- Color Preview -->
        <div class="ui-color-picker__preview-container">
          <div 
            class="ui-color-picker__preview"
            [style.background-color]="getHexColor()"
            [attr.aria-label]="'Selected color: ' + getHexColor()">
          </div>
        </div>
        
        <!-- Hex Input -->
        <div class="ui-color-picker__hex-container">
          <label class="ui-color-picker__hex-label" [for]="pickerId + '-hex'">HEX</label>
          <input
            #hexInput
            type="text"
            class="ui-color-picker__hex-input"
            [id]="pickerId + '-hex'"
            [value]="getHexColor()"
            [disabled]="disabled"
            [attr.maxlength]="7"
            [attr.pattern]="'^#[0-9A-Fa-f]{6}$'"
            (input)="handleHexInput($event)"
            (blur)="handleHexBlur($event)"
          />
        </div>
      </div>
      
      @if (description) {
        <div class="ui-color-picker__description" [id]="pickerId + '-desc'">
          {{ description }}
        </div>
      }
    </div>
  `,
  styleUrl: './color-picker.component.scss'
})
export class ColorPickerComponent implements ControlValueAccessor, AfterViewInit, OnDestroy {
  @Input() label: string = '';
  @Input() description: string = '';
  @Input() size: ColorPickerSize = 'md';
  @Input() variant: ColorPickerVariant = 'primary';
  @Input() state: ColorPickerState = 'default';
  @Input() disabled = false;
  @Input() required = false;
  @Input() pickerId: string = `color-picker-${Math.random().toString(36).substr(2, 9)}`;

  @Output() colorChange = new EventEmitter<string>();
  @Output() colorFocus = new EventEmitter<FocusEvent>();
  @Output() colorBlur = new EventEmitter<FocusEvent>();

  @ViewChild('colorWheel') colorWheelCanvas!: ElementRef<HTMLCanvasElement>;
  @ViewChild('hueBar') hueBarCanvas!: ElementRef<HTMLCanvasElement>;
  @ViewChild('hexInput') hexInputElement!: ElementRef<HTMLInputElement>;

  protected currentColor = signal<HSL>({ h: 0, s: 100, l: 50 });
  private isDraggingWheel = false;
  private isDraggingHue = false;
  private wheelCtx?: CanvasRenderingContext2D;
  private hueCtx?: CanvasRenderingContext2D;

  // ControlValueAccessor implementation
  private onChange = (value: string) => {};
  private onTouched = () => {};

  ngAfterViewInit(): void {
    this.initializeCanvases();
    this.drawColorWheel();
    this.drawHueBar();
    this.setupEventListeners();
  }

  ngOnDestroy(): void {
    this.removeEventListeners();
  }

  writeValue(value: string): void {
    if (value && this.isValidHexColor(value)) {
      const hsl = this.hexToHsl(value);
      this.currentColor.set(hsl);
      this.updateCanvases();
    }
  }

  registerOnChange(fn: (value: string) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  wheelSize(): number {
    switch (this.size) {
      case 'sm': return 120;
      case 'lg': return 200;
      default: return 160;
    }
  }

  hueBarWidth(): number {
    return this.wheelSize();
  }

  hueBarHeight(): number {
    switch (this.size) {
      case 'sm': return 16;
      case 'lg': return 24;
      default: return 20;
    }
  }

  getWrapperClasses(): string {
    const classes = [
      `ui-color-picker--${this.size}`,
      `ui-color-picker--${this.variant}`,
      `ui-color-picker--${this.state}`
    ];

    if (this.disabled) classes.push('ui-color-picker--disabled');
    if (this.required) classes.push('ui-color-picker--required');

    return classes.join(' ');
  }

  wheelIndicatorPosition(): { x: number, y: number } {
    const size = this.wheelSize();
    const radius = size / 2;
    const center = radius;
    
    // Convert HSL to position on wheel
    const saturation = this.currentColor().s / 100;
    const lightness = this.currentColor().l / 100;
    
    // Calculate distance from center (0 = center, 1 = edge)
    const distance = saturation * (radius - 8);
    
    // Calculate angle for lightness (0 = top, increases clockwise)
    const angle = (lightness * 360 - 90) * (Math.PI / 180);
    
    return {
      x: center + Math.cos(angle) * distance - 6,
      y: center + Math.sin(angle) * distance - 6
    };
  }

  hueIndicatorPosition(): number {
    const width = this.hueBarWidth();
    return (this.currentColor().h / 360) * width - 6;
  }

  getHexColor(): string {
    return this.hslToHex(this.currentColor());
  }

  getWheelAriaText(): string {
    const { s, l } = this.currentColor();
    return `Saturation ${Math.round(s)}%, Lightness ${Math.round(l)}%`;
  }

  private initializeCanvases(): void {
    if (this.colorWheelCanvas?.nativeElement) {
      this.wheelCtx = this.colorWheelCanvas.nativeElement.getContext('2d') || undefined;
    }
    if (this.hueBarCanvas?.nativeElement) {
      this.hueCtx = this.hueBarCanvas.nativeElement.getContext('2d') || undefined;
    }
  }

  private drawColorWheel(): void {
    if (!this.wheelCtx) return;

    const size = this.wheelSize();
    const radius = size / 2;
    const center = radius;
    
    this.wheelCtx.clearRect(0, 0, size, size);
    
    // Create saturation/lightness wheel with current hue
    for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
        const dx = x - center;
        const dy = y - center;
        const distance = Math.sqrt(dx * dx + dy * dy);
        
        if (distance <= radius - 8) {
          const angle = Math.atan2(dy, dx);
          const saturation = (distance / (radius - 8)) * 100;
          const lightness = ((angle + Math.PI) / (2 * Math.PI)) * 100;
          
          const color = this.hslToRgb({
            h: this.currentColor().h,
            s: saturation,
            l: lightness
          });
          
          this.wheelCtx.fillStyle = `rgb(${color.r}, ${color.g}, ${color.b})`;
          this.wheelCtx.fillRect(x, y, 1, 1);
        }
      }
    }
  }

  private drawHueBar(): void {
    if (!this.hueCtx) return;

    const width = this.hueBarWidth();
    const height = this.hueBarHeight();
    
    this.hueCtx.clearRect(0, 0, width, height);
    
    // Create hue gradient
    const gradient = this.hueCtx.createLinearGradient(0, 0, width, 0);
    for (let i = 0; i <= 360; i += 30) {
      gradient.addColorStop(i / 360, `hsl(${i}, 100%, 50%)`);
    }
    
    this.hueCtx.fillStyle = gradient;
    this.hueCtx.fillRect(0, 0, width, height);
  }

  private updateCanvases(): void {
    this.drawColorWheel();
    // Hue bar doesn't need redrawing unless size changes
  }

  private setupEventListeners(): void {
    document.addEventListener('mousemove', this.handleMouseMove.bind(this));
    document.addEventListener('mouseup', this.handleMouseUp.bind(this));
    document.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
    document.addEventListener('touchend', this.handleTouchEnd.bind(this));
  }

  private removeEventListeners(): void {
    document.removeEventListener('mousemove', this.handleMouseMove.bind(this));
    document.removeEventListener('mouseup', this.handleMouseUp.bind(this));
    document.removeEventListener('touchmove', this.handleTouchMove.bind(this));
    document.removeEventListener('touchend', this.handleTouchEnd.bind(this));
  }

  startWheelDrag(event: MouseEvent): void {
    if (this.disabled) return;
    
    this.isDraggingWheel = true;
    this.updateWheelFromEvent(event);
    event.preventDefault();
  }

  startHueDrag(event: MouseEvent): void {
    if (this.disabled) return;
    
    this.isDraggingHue = true;
    this.updateHueFromEvent(event);
    event.preventDefault();
  }

  private handleMouseMove(event: MouseEvent): void {
    if (this.isDraggingWheel) {
      this.updateWheelFromEvent(event);
    } else if (this.isDraggingHue) {
      this.updateHueFromEvent(event);
    }
  }

  private handleMouseUp(): void {
    this.isDraggingWheel = false;
    this.isDraggingHue = false;
  }

  private handleTouchMove(event: TouchEvent): void {
    event.preventDefault();
    const touch = event.touches[0];
    if (this.isDraggingWheel) {
      this.updateWheelFromEvent(touch);
    } else if (this.isDraggingHue) {
      this.updateHueFromEvent(touch);
    }
  }

  private handleTouchEnd(): void {
    this.isDraggingWheel = false;
    this.isDraggingHue = false;
  }

  private updateWheelFromEvent(event: MouseEvent | Touch): void {
    if (!this.colorWheelCanvas?.nativeElement) return;

    const rect = this.colorWheelCanvas.nativeElement.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    
    const size = this.wheelSize();
    const radius = size / 2;
    const center = radius;
    
    const dx = x - center;
    const dy = y - center;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    if (distance <= radius - 8) {
      const angle = Math.atan2(dy, dx);
      const saturation = Math.min(100, (distance / (radius - 8)) * 100);
      const lightness = ((angle + Math.PI) / (2 * Math.PI)) * 100;
      
      const newColor = {
        ...this.currentColor(),
        s: saturation,
        l: lightness
      };
      
      this.currentColor.set(newColor);
      this.emitColorChange();
    }
  }

  private updateHueFromEvent(event: MouseEvent | Touch): void {
    if (!this.hueBarCanvas?.nativeElement) return;

    const rect = this.hueBarCanvas.nativeElement.getBoundingClientRect();
    const x = event.clientX - rect.left;
    
    const width = this.hueBarWidth();
    const hue = Math.max(0, Math.min(360, (x / width) * 360));
    
    const newColor = {
      ...this.currentColor(),
      h: hue
    };
    
    this.currentColor.set(newColor);
    this.drawColorWheel(); // Redraw wheel with new hue
    this.emitColorChange();
  }

  handleWheelKeydown(event: KeyboardEvent): void {
    if (this.disabled) return;

    const step = event.shiftKey ? 10 : 1;
    let { h, s, l } = this.currentColor();
    
    switch (event.key) {
      case 'ArrowUp':
        l = Math.min(100, l + step);
        break;
      case 'ArrowDown':
        l = Math.max(0, l - step);
        break;
      case 'ArrowLeft':
        s = Math.max(0, s - step);
        break;
      case 'ArrowRight':
        s = Math.min(100, s + step);
        break;
      default:
        return;
    }
    
    this.currentColor.set({ h, s, l });
    this.emitColorChange();
    event.preventDefault();
  }

  handleHueKeydown(event: KeyboardEvent): void {
    if (this.disabled) return;

    const step = event.shiftKey ? 10 : 1;
    let { h, s, l } = this.currentColor();
    
    switch (event.key) {
      case 'ArrowLeft':
        h = (h - step + 360) % 360;
        break;
      case 'ArrowRight':
        h = (h + step) % 360;
        break;
      default:
        return;
    }
    
    this.currentColor.set({ h, s, l });
    this.drawColorWheel();
    this.emitColorChange();
    event.preventDefault();
  }

  handleHexInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    let value = input.value.trim();
    
    // Add # if missing
    if (value && !value.startsWith('#')) {
      value = '#' + value;
    }
    
    if (this.isValidHexColor(value)) {
      const hsl = this.hexToHsl(value);
      this.currentColor.set(hsl);
      this.updateCanvases();
      this.emitColorChange();
    }
  }

  handleHexBlur(event: FocusEvent): void {
    this.onTouched();
    this.colorBlur.emit(event);
    
    // Reset to valid color if invalid input
    const input = event.target as HTMLInputElement;
    if (!this.isValidHexColor(input.value)) {
      input.value = this.getHexColor();
    }
  }

  private emitColorChange(): void {
    const hex = this.getHexColor();
    this.onChange(hex);
    this.colorChange.emit(hex);
  }

  private isValidHexColor(hex: string): boolean {
    return /^#[0-9A-Fa-f]{6}$/.test(hex);
  }

  private hslToRgb(hsl: HSL): RGB {
    const h = hsl.h / 360;
    const s = hsl.s / 100;
    const l = hsl.l / 100;
    
    let r, g, b;
    
    if (s === 0) {
      r = g = b = l;
    } else {
      const hue2rgb = (p: number, q: number, t: number) => {
        if (t < 0) t += 1;
        if (t > 1) t -= 1;
        if (t < 1/6) return p + (q - p) * 6 * t;
        if (t < 1/2) return q;
        if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
        return p;
      };
      
      const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
      const p = 2 * l - q;
      
      r = hue2rgb(p, q, h + 1/3);
      g = hue2rgb(p, q, h);
      b = hue2rgb(p, q, h - 1/3);
    }
    
    return {
      r: Math.round(r * 255),
      g: Math.round(g * 255),
      b: Math.round(b * 255)
    };
  }

  private rgbToHsl(rgb: RGB): HSL {
    const r = rgb.r / 255;
    const g = rgb.g / 255;
    const b = rgb.b / 255;
    
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const diff = max - min;
    
    let h = 0;
    let s = 0;
    const l = (max + min) / 2;
    
    if (diff !== 0) {
      s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
      
      switch (max) {
        case r:
          h = (g - b) / diff + (g < b ? 6 : 0);
          break;
        case g:
          h = (b - r) / diff + 2;
          break;
        case b:
          h = (r - g) / diff + 4;
          break;
      }
      h /= 6;
    }
    
    return {
      h: Math.round(h * 360),
      s: Math.round(s * 100),
      l: Math.round(l * 100)
    };
  }

  private hexToHsl(hex: string): HSL {
    const rgb = this.hexToRgb(hex);
    return this.rgbToHsl(rgb);
  }

  private hslToHex(hsl: HSL): string {
    const rgb = this.hslToRgb(hsl);
    return this.rgbToHex(rgb);
  }

  private hexToRgb(hex: string): RGB {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return { r, g, b };
  }

  private rgbToHex(rgb: RGB): string {
    const toHex = (n: number) => n.toString(16).padStart(2, '0');
    return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`.toUpperCase();
  }
}