import { EQBand } from '../../studio/types';
import {
  BiQuadFilterMode,
  getBiQuadFilterTransferFunction,
} from '../getBiQuadFilterTransferFunction';
import { EQ_COLORS } from './eqComponents';
import {
  MAX_FREQ,
  MAX_GAIN,
  MIN_FREQ,
  MIN_GAIN,
  SAMPLE_RATE,
} from './eqConstants';
import { freqToPosition, gainToPosition } from './eqUtils';

interface CanvasSetup {
  ctx: CanvasRenderingContext2D;
  width: number;
  height: number;
  zeroY: number;
}

// Map EQ band types to BiQuad filter modes
const FILTER_MODE_MAP: Record<string, BiQuadFilterMode> = {
  highpass: BiQuadFilterMode.HIGHPASS,
  lowshelf: BiQuadFilterMode.LOW_SHELF,
  peaking: BiQuadFilterMode.EQ_BAND,
  notch: BiQuadFilterMode.NOTCH,
  highshelf: BiQuadFilterMode.HIGH_SHELF,
  lowpass: BiQuadFilterMode.LOWPASS,
};

/**
 * Set up canvas with proper DPI scaling and return rendering context
 */
function setupCanvas(canvas: HTMLCanvasElement): CanvasSetup | null {
  const ctx = canvas.getContext('2d');
  if (!ctx) return null;

  // Set canvas resolution with device pixel ratio
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  ctx.scale(dpr, dpr);

  const width = rect.width;
  const height = rect.height;

  // Clear canvas
  ctx.clearRect(0, 0, width, height);

  // Calculate zero line position
  const zeroY = height * (1 - gainToPosition(0));

  return { ctx, width, height, zeroY };
}

/**
 * Draw grid lines for frequency and gain
 */
export function drawGrid(
  ctx: CanvasRenderingContext2D,
  width: number,
  height: number
): void {
  ctx.strokeStyle = EQ_COLORS.darkGray;
  ctx.lineWidth = 1;

  // Horizontal grid lines (dB) - every 6dB
  // Start from a multiple of 6 that includes 0 dB
  const gridStep = 6;
  const startDb = Math.floor(MIN_GAIN / gridStep) * gridStep;
  for (let db = startDb; db <= MAX_GAIN; db += gridStep) {
    // Skip 0 dB since it's drawn separately as the center line
    if (db === 0) continue;

    const y = height * (1 - gainToPosition(db));
    ctx.beginPath();
    ctx.moveTo(0, y);
    ctx.lineTo(width, y);
    ctx.stroke();
  }

  // Vertical grid lines (frequency)
  const freqSteps = [60, 160, 450, 1200, 3200, 8800];
  freqSteps.forEach((freq) => {
    if (freq >= MIN_FREQ && freq <= MAX_FREQ) {
      const x = width * freqToPosition(freq);
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, height);
      ctx.stroke();
    }
  });
}

/**
 * Draw center line at 0 dB
 */
export function drawCenterLine(
  ctx: CanvasRenderingContext2D,
  width: number,
  zeroY: number
): void {
  ctx.strokeStyle = `color-mix(in srgb, ${EQ_COLORS.white} 20%, transparent)`;
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(0, zeroY);
  ctx.lineTo(width, zeroY);
  ctx.stroke();
}

interface BandTransferFunction {
  tf: number[][];
  bandKey: 'band1' | 'band2' | 'band3' | 'band4' | 'band5' | 'band6';
}

/**
 * Get transfer functions for gain-based bands (peaking, lowshelf, highshelf)
 */
function getBandTransferFunctions(
  bands: EQBand[],
  bandKeys: Array<'band1' | 'band2' | 'band3' | 'band4' | 'band5' | 'band6'>
): BandTransferFunction[] {
  const result: BandTransferFunction[] = [];

  bands.forEach((band, index) => {
    // Skip disabled bands
    if (!band.enabled) {
      return;
    }

    // Only process gain-based bands
    if (
      band.type !== 'peaking' &&
      band.type !== 'lowshelf' &&
      band.type !== 'highshelf'
    ) {
      return;
    }

    const mode = FILTER_MODE_MAP[band.type] ?? BiQuadFilterMode.EQ_BAND;

    result.push({
      tf: getBiQuadFilterTransferFunction(
        SAMPLE_RATE,
        mode,
        band.frequency,
        band.q,
        band.gain
      ),
      bandKey: bandKeys[index],
    });
  });

  return result;
}

/**
 * Get transfer functions for enabled filter-type bands (highpass, lowpass, notch)
 */
function getFilterTransferFunctions(
  bands: EQBand[],
  bandKeys: Array<'band1' | 'band2' | 'band3' | 'band4' | 'band5' | 'band6'>
): BandTransferFunction[] {
  const result: BandTransferFunction[] = [];

  bands.forEach((band, index) => {
    // Skip disabled bands
    if (!band.enabled) {
      return;
    }

    // Only process pure filter types (not gain-based types)
    // Skip gain-based bands (handled separately)
    if (
      band.type === 'lowshelf' ||
      band.type === 'highshelf' ||
      band.type === 'peaking'
    ) {
      return;
    }

    const mode = FILTER_MODE_MAP[band.type];
    if (mode === undefined) {
      return;
    }

    result.push({
      tf: getBiQuadFilterTransferFunction(
        SAMPLE_RATE,
        mode,
        band.frequency,
        band.q,
        0 // gain not used for pure filters
      ),
      bandKey: bandKeys[index],
    });
  });

  return result;
}

/**
 * Draw a filled curve for a single transfer function
 */
function drawFilledCurve(
  ctx: CanvasRenderingContext2D,
  transferFunction: number[][],
  width: number,
  height: number,
  zeroY: number,
  isDragging: boolean,
  enabled: boolean
): void {
  const numPoints = transferFunction.length;
  if (numPoints === 0) return;

  // Set fill style based on drag state and enabled state
  const color = enabled ? EQ_COLORS.primaryBlue : EQ_COLORS.disabledGray;
  ctx.fillStyle = isDragging
    ? `color-mix(in srgb, ${color} 70%, transparent)`
    : `color-mix(in srgb, ${color} 15%, transparent)`;

  ctx.beginPath();

  // Start at the beginning on the zero line
  const [firstNormPos] = transferFunction[0];
  const firstX = width * firstNormPos * 2;
  ctx.moveTo(firstX, zeroY);

  // Draw along the transfer function curve
  for (let i = 0; i < numPoints; i++) {
    const [normPos, db] = transferFunction[i];
    const x = width * normPos * 2;
    const gainPos = gainToPosition(
      Math.max(MIN_GAIN - 2, Math.min(MAX_GAIN + 2, db))
    );
    const y = height * (1 - gainPos);
    ctx.lineTo(x, y);
  }

  // Close the path back to the zero line
  const [lastNormPos] = transferFunction[numPoints - 1];
  const lastX = width * lastNormPos * 2;
  ctx.lineTo(lastX, zeroY);
  ctx.closePath();
  ctx.fill();
}

/**
 * Draw filled areas for all individual EQ bands (both gain-based and filter-based)
 */
function drawBandCurves(
  ctx: CanvasRenderingContext2D,
  bandTransferFunctions: BandTransferFunction[],
  width: number,
  height: number,
  zeroY: number,
  draggingBandKey:
    | 'band1'
    | 'band2'
    | 'band3'
    | 'band4'
    | 'band5'
    | 'band6'
    | null,
  enabled: boolean
): void {
  bandTransferFunctions.forEach(({ tf, bandKey }) => {
    const isDragging = draggingBandKey === bandKey;
    drawFilledCurve(ctx, tf, width, height, zeroY, isDragging, enabled);
  });
}

/**
 * Draw the aggregate transfer function as a solid line
 */
function drawAggregateCurve(
  ctx: CanvasRenderingContext2D,
  allTransferFunctions: BandTransferFunction[],
  width: number,
  height: number,
  enabled: boolean
): void {
  // Get number of points from first available transfer function
  const numPoints = allTransferFunctions[0]?.tf.length ?? 0;
  if (numPoints === 0) return;

  ctx.strokeStyle = enabled ? EQ_COLORS.primaryBlue : EQ_COLORS.disabledGray;
  ctx.lineWidth = 2;
  ctx.beginPath();

  for (let i = 0; i < numPoints; i++) {
    // Get normPos from first transfer function
    const [normPos] = allTransferFunctions[0].tf[i];

    // Sum dB values from all transfer functions at this point
    let totalDb = 0;
    allTransferFunctions.forEach(({ tf }) => {
      if (tf[i]) {
        totalDb += tf[i][1];
      }
    });

    // Map normPos (0 to 0.5) to full canvas width
    const x = width * normPos * 2;
    const gainPos = gainToPosition(
      Math.max(MIN_GAIN - 2, Math.min(MAX_GAIN + 2, totalDb))
    );
    const y = height * (1 - gainPos);

    if (i === 0) {
      ctx.moveTo(x, y);
    } else {
      ctx.lineTo(x, y);
    }
  }

  ctx.stroke();
}

/**
 * Draw EQ curves on an already-setup canvas context (without clearing)
 */
export function drawEQCurvesOnContext(
  ctx: CanvasRenderingContext2D,
  width: number,
  height: number,
  bands: EQBand[],
  draggingBandKey:
    | 'band1'
    | 'band2'
    | 'band3'
    | 'band4'
    | 'band5'
    | 'band6'
    | null,
  enabled: boolean
): void {
  // Calculate zero line position
  const zeroY = height * (1 - gainToPosition(0));

  // Create band keys array
  const bandKeys: Array<
    'band1' | 'band2' | 'band3' | 'band4' | 'band5' | 'band6'
  > = ['band1', 'band2', 'band3', 'band4', 'band5', 'band6'];

  // Get all transfer functions (both gain-based and filter-based)
  const gainBasedTransferFunctions = getBandTransferFunctions(bands, bandKeys);
  const filterBasedTransferFunctions = getFilterTransferFunctions(
    bands,
    bandKeys
  );

  // Combine all transfer functions
  const allTransferFunctions = [
    ...gainBasedTransferFunctions,
    ...filterBasedTransferFunctions,
  ];

  // Early return if no data at all
  if (allTransferFunctions.length === 0) {
    return;
  }

  // Early return if transfer functions are invalid
  if (!allTransferFunctions[0]?.tf || allTransferFunctions[0].tf.length === 0) {
    return;
  }

  // Draw individual curves
  drawBandCurves(
    ctx,
    allTransferFunctions,
    width,
    height,
    zeroY,
    draggingBandKey,
    enabled
  );

  // Draw aggregate curve
  drawAggregateCurve(ctx, allTransferFunctions, width, height, enabled);
}

/**
 * Main function to render the complete EQ curve visualization
 */
export function drawEQCurve(
  canvas: HTMLCanvasElement | null,
  bands: EQBand[],
  draggingBandKey:
    | 'band1'
    | 'band2'
    | 'band3'
    | 'band4'
    | 'band5'
    | 'band6'
    | null,
  enabled: boolean
): void {
  if (!canvas) return;

  const setup = setupCanvas(canvas);
  if (!setup) return;

  const { ctx, width, height } = setup;

  drawEQCurvesOnContext(ctx, width, height, bands, draggingBandKey, enabled);
}
