import type { SkRRect, Skia } from "../../../skia/types";
import { BlurStyle, ClipOp, isRRect } from "../../../skia/types";
import type { DrawingContext } from "../../types";
import { DeclarationType, NodeType } from "../../types";
import type { BoxShadowProps, BoxProps } from "../../types/Drawings";
import type { NodeContext } from "../Node";
import { JsiDeclarationNode } from "../Node";
import { JsiRenderNode } from "../RenderNode";
import type { DeclarationContext } from "../../types/DeclarationContext";

const inflate = (
  Skia: Skia,
  box: SkRRect,
  dx: number,
  dy: number,
  tx = 0,
  ty = 0
) =>
  Skia.RRectXY(
    Skia.XYWHRect(
      box.rect.x - dx + tx,
      box.rect.y - dy + ty,
      box.rect.width + 2 * dx,
      box.rect.height + 2 * dy
    ),
    box.rx + dx,
    box.ry + dy
  );

const deflate = (
  Skia: Skia,
  box: SkRRect,
  dx: number,
  dy: number,
  tx = 0,
  ty = 0
) => inflate(Skia, box, -dx, -dy, tx, ty);

export class BoxShadowNode extends JsiDeclarationNode<BoxShadowProps> {
  constructor(ctx: NodeContext, props: BoxShadowProps) {
    super(ctx, DeclarationType.Unknown, NodeType.BoxShadow, props);
  }

  decorate(_ctx: DeclarationContext) {
    // do nothing
  }

  materialize() {
    return this.props;
  }
}

export class BoxNode extends JsiRenderNode<BoxProps> {
  constructor(ctx: NodeContext, props: BoxProps) {
    super(ctx, NodeType.Box, props);
  }

  renderNode({ canvas, paint }: DrawingContext) {
    const { box: defaultBox } = this.props;
    const opacity = paint.getAlphaf();
    const box = isRRect(defaultBox)
      ? defaultBox
      : this.Skia.RRectXY(defaultBox, 0, 0);
    const shadows = this._children
      .map((node) => {
        if (node instanceof BoxShadowNode) {
          return node.materialize();
        }
        return null;
      })
      .filter((n): n is BoxShadowProps => n !== null);
    shadows
      .filter((shadow) => !shadow.inner)
      .map((shadow) => {
        const { color = "black", blur, spread = 0, dx = 0, dy = 0 } = shadow;
        const lPaint = this.Skia.Paint();
        lPaint.setColor(this.Skia.Color(color));
        lPaint.setAlphaf(paint.getAlphaf() * opacity);
        lPaint.setMaskFilter(
          this.Skia.MaskFilter.MakeBlur(BlurStyle.Normal, blur, true)
        );
        canvas.drawRRect(
          inflate(this.Skia, box, spread, spread, dx, dy),
          lPaint
        );
      });

    canvas.drawRRect(box, paint);

    shadows
      .filter((shadow) => shadow.inner)
      .map((shadow) => {
        const { color = "black", blur, spread = 0, dx = 0, dy = 0 } = shadow;
        const delta = this.Skia.Point(10 + Math.abs(dx), 10 + Math.abs(dy));
        canvas.save();
        canvas.clipRRect(box, ClipOp.Intersect, false);
        const lPaint = this.Skia.Paint();
        lPaint.setColor(this.Skia.Color(color));
        lPaint.setAlphaf(paint.getAlphaf() * opacity);

        lPaint.setMaskFilter(
          this.Skia.MaskFilter.MakeBlur(BlurStyle.Normal, blur, true)
        );
        const inner = deflate(this.Skia, box, spread, spread, dx, dy);
        const outer = inflate(this.Skia, box, delta.x, delta.y);
        canvas.drawDRRect(outer, inner, lPaint);
        canvas.restore();
      });
  }
}
