import { enumKey, processPath } from "../../../dom/nodes";
import { NodeType } from "../../../dom/types";
import type {
  CornerPathEffectProps,
  DashPathEffectProps,
  DiscretePathEffectProps,
  Line2DPathEffectProps,
  Path1DPathEffectProps,
  Path2DPathEffectProps,
} from "../../../dom/types";
import { Path1DEffectStyle } from "../../../skia/types";
import { composeDeclarations } from "../../utils";
import type { Command } from "../Core";
import { CommandType } from "../Core";
import type { DrawingContext } from "../DrawingContext";

const declareDiscretePathEffect = (
  ctx: DrawingContext,
  props: DiscretePathEffectProps
) => {
  "worklet";
  const { length, deviation, seed } = props;
  const pe = ctx.Skia.PathEffect.MakeDiscrete(length, deviation, seed);
  ctx.pathEffects.push(pe);
};

const declarePath2DPathEffect = (
  ctx: DrawingContext,
  props: Path2DPathEffectProps
) => {
  "worklet";
  const { matrix } = props;
  const path = processPath(ctx.Skia, props.path);
  const pe = ctx.Skia.PathEffect.MakePath2D(matrix, path);
  if (pe === null) {
    throw new Error("Path2DPathEffect: invalid path");
  }
  ctx.pathEffects.push(pe);
};

const declareDashPathEffect = (
  ctx: DrawingContext,
  props: DashPathEffectProps
) => {
  "worklet";
  const { intervals, phase } = props;
  const pe = ctx.Skia.PathEffect.MakeDash(intervals, phase);
  ctx.pathEffects.push(pe);
};

const declareCornerPathEffect = (
  ctx: DrawingContext,
  props: CornerPathEffectProps
) => {
  "worklet";
  const { r } = props;
  const pe = ctx.Skia.PathEffect.MakeCorner(r);
  if (pe === null) {
    throw new Error("CornerPathEffect: couldn't create path effect");
  }
  ctx.pathEffects.push(pe);
};

const declareSumPathEffect = (ctx: DrawingContext) => {
  "worklet";
  // Note: decorateChildren functionality needs to be handled differently
  const pes = ctx.pathEffects.splice(0, ctx.pathEffects.length);
  const pe = composeDeclarations(
    pes,
    ctx.Skia.PathEffect.MakeSum.bind(ctx.Skia.PathEffect)
  );
  ctx.pathEffects.push(pe);
};

const declareLine2DPathEffect = (
  ctx: DrawingContext,
  props: Line2DPathEffectProps
) => {
  "worklet";
  const { width, matrix } = props;
  const pe = ctx.Skia.PathEffect.MakeLine2D(width, matrix);
  if (pe === null) {
    throw new Error("Line2DPathEffect: could not create path effect");
  }
  ctx.pathEffects.push(pe);
};

const declarePath1DPathEffect = (
  ctx: DrawingContext,
  props: Path1DPathEffectProps
) => {
  "worklet";
  const { advance, phase, style } = props;
  const path = processPath(ctx.Skia, props.path);
  const pe = ctx.Skia.PathEffect.MakePath1D(
    path,
    advance,
    phase,
    Path1DEffectStyle[enumKey(style)]
  );
  if (pe === null) {
    throw new Error("Path1DPathEffect: could not create path effect");
  }
  ctx.pathEffects.push(pe);
};

export const isPushPathEffect = (
  command: Command
): command is Command<CommandType.PushPathEffect> => {
  "worklet";
  return command.type === CommandType.PushPathEffect;
};

type Props = {
  [NodeType.DiscretePathEffect]: DiscretePathEffectProps;
  [NodeType.DashPathEffect]: DashPathEffectProps;
  [NodeType.Path1DPathEffect]: Path1DPathEffectProps;
  [NodeType.Path2DPathEffect]: Path2DPathEffectProps;
  [NodeType.CornerPathEffect]: CornerPathEffectProps;
  [NodeType.SumPathEffect]: Record<string, never>;
  [NodeType.Line2DPathEffect]: Line2DPathEffectProps;
};

interface PushPathEffect<
  T extends keyof Props,
> extends Command<CommandType.PushPathEffect> {
  pathEffectType: T;
  props: Props[T];
}

const isPathEffect = <T extends keyof Props>(
  command: Command<CommandType.PushPathEffect>,
  type: T
): command is PushPathEffect<T> => {
  "worklet";
  return command.pathEffectType === type;
};

export const composePathEffects = (ctx: DrawingContext) => {
  "worklet";
  if (ctx.pathEffects.length > 1) {
    const outer = ctx.pathEffects.pop()!;
    const inner = ctx.pathEffects.pop()!;
    ctx.pathEffects.push(ctx.Skia.PathEffect.MakeCompose(outer, inner));
  }
};

export const pushPathEffect = (
  ctx: DrawingContext,
  command: Command<CommandType.PushPathEffect>
) => {
  "worklet";
  if (isPathEffect(command, NodeType.DiscretePathEffect)) {
    declareDiscretePathEffect(ctx, command.props);
  } else if (isPathEffect(command, NodeType.DashPathEffect)) {
    declareDashPathEffect(ctx, command.props);
  } else if (isPathEffect(command, NodeType.Path1DPathEffect)) {
    declarePath1DPathEffect(ctx, command.props);
  } else if (isPathEffect(command, NodeType.Path2DPathEffect)) {
    declarePath2DPathEffect(ctx, command.props);
  } else if (isPathEffect(command, NodeType.CornerPathEffect)) {
    declareCornerPathEffect(ctx, command.props);
  } else if (isPathEffect(command, NodeType.SumPathEffect)) {
    declareSumPathEffect(ctx);
  } else if (isPathEffect(command, NodeType.Line2DPathEffect)) {
    declareLine2DPathEffect(ctx, command.props);
  } else {
    throw new Error("Invalid image filter type: " + command.imageFilterType);
  }
};
