#import "@preview/cetz:0.3.4"

#let draw-strand(polyline, size-factor: 1.0, style: (:)) = {
  import cetz.draw: *

  // draw the white outline
  set-style(..cetz.styles.resolve(
    (stroke: (paint: white, thickness: size-factor * 0.75pt * 8, cap: "butt")),
    base: style,
  ))
  polyline

  // draw the black line
  set-style(..cetz.styles.resolve(style, base: (stroke: (paint: black, thickness: size-factor * 0.75pt, cap: "round"))))
  polyline
  // set-style(stroke: (paint: black, thickness: thickness, cap: "round"))
}

#let skein-canvas = (body, size-factor: 1.0) => cetz.canvas(length: size-factor * 0.25cm, padding: 0.25, {
  import cetz.draw: *
  rect((-1, -1), (1, 1), fill: white, stroke: none)

  body
})

#let arrow-size = 0.35

#let draw-arrow((x, y), angle: 0, ..style) = {
  import cetz.draw: *

  {
    let len = arrow-size / calc.sqrt(2)
    set-style(..style)

    translate(x: x, y: y)
    rotate(z: angle)
    line((0, 0), (-len, +len), ..style)
    line((0, 0), (-len, -len), ..style)
  }
}

#let medium-scale-factor = 0.8
#let small-scale-factor = 0.125cm / 0.25cm

#let skein = (
  unit: skein-canvas({
    import cetz.draw: *

    circle((0, 0), radius: 1, stroke: (paint: black, thickness: 0.75pt))
  }),
  over: skein-canvas({
    import cetz.draw: *
    draw-strand({ line((-1, -1), (1, 1)) })
    draw-strand({ line((-1, 1), (1, -1)) })
  }),
  under: skein-canvas({
    import cetz.draw: *
    draw-strand({ line((-1, 1), (1, -1)) })
    draw-strand({ line((-1, -1), (1, 1)) })
  }),
  h: skein-canvas({
    import cetz.draw: *
    draw-strand({ hobby((-1, -1), (0, -0.61), (1, -1), omega: 1) })
    draw-strand({ hobby((-1, 1), (0, +0.61), (1, 1), omega: 1) })
  }),
  v: skein-canvas({
    import cetz.draw: *
    draw-strand({ hobby((-1, -1), (-0.61, 0), (-1, 1), omega: 1) })
    draw-strand({ hobby((1, -1), (+0.61, 0), (1, 1), omega: 1) })
  }),
  strand: skein-canvas({
    import cetz.draw: *
    rect((-1, -1), (1, 1), fill: white, stroke: none)
    draw-strand({ hobby((-1, 0), (0, 0.25), (1, 0), omega: 1) })
  }),
  over-twist: skein-canvas({
    import cetz.draw: *
    draw-strand({ hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
    draw-strand({ hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
  }),
  under-twist: skein-canvas({
    import cetz.draw: *
    draw-strand({ hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
    draw-strand({ hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
  }),
  //
  // Large
  //
  unit-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *

    circle((0, 0), radius: 1, stroke: (paint: black, thickness: 0.75pt))
  }),
  over-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ line((-1, -1), (1, 1)) })
    draw-strand({ line((-1, 1), (1, -1)) })
  }),
  under-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ line((-1, 1), (1, -1)) })
    draw-strand({ line((-1, -1), (1, 1)) })
  }),
  h-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ hobby((-1, -1), (0, -0.61), (1, -1), omega: 1) })
    draw-strand({ hobby((-1, 1), (0, +0.61), (1, 1), omega: 1) })
  }),
  v-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ hobby((-1, -1), (-0.61, 0), (-1, 1), omega: 1) })
    draw-strand({ hobby((1, -1), (+0.61, 0), (1, 1), omega: 1) })
  }),
  strand-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    rect((-1, -1), (1, 1), fill: white, stroke: none)
    draw-strand({ hobby((-1, 0), (0, 0.25), (1, 0), omega: 1) })
  }),
  over-twist-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
    draw-strand({ hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
  }),
  under-twist-large: skein-canvas(size-factor: 1.5, {
    import cetz.draw: *
    draw-strand({ hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
    draw-strand({ hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
  }),
  //
  // Medium
  //
  unit-medium: skein-canvas(size-factor: medium-scale-factor, {
    import cetz.draw: *

    circle((0, 0), radius: 1, stroke: (paint: black, thickness: 0.75pt))
  }),
  strand-medium: skein-canvas(size-factor: medium-scale-factor, {
    import cetz.draw: *
    rect((-1, -1), (1, 1), fill: white, stroke: none)
    draw-strand(size-factor: medium-scale-factor, { hobby((-1, 0), (0, 0.25), (1, 0), omega: 1) })
  }),
  over-twist-medium: skein-canvas(size-factor: medium-scale-factor, {
    import cetz.draw: *
    draw-strand(size-factor: medium-scale-factor, { hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
    draw-strand(size-factor: medium-scale-factor, { hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
  }),
  under-twist-medium: skein-canvas(size-factor: medium-scale-factor, {
    import cetz.draw: *
    draw-strand(size-factor: small-scale-factor, { hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
    draw-strand(size-factor: small-scale-factor, { hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
  }),
  //
  // Small
  //
  strand-small: skein-canvas(size-factor: small-scale-factor, {
    import cetz.draw: *
    rect((-1, -1), (1, 1), fill: white, stroke: none)
    draw-strand(size-factor: small-scale-factor, { hobby((-1, 0), (0, 0.25), (1, 0), omega: 1) })
  }),
  over-twist-small: skein-canvas(size-factor: small-scale-factor, {
    import cetz.draw: *
    draw-strand(size-factor: small-scale-factor, { hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
    draw-strand(size-factor: small-scale-factor, { hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
  }),
  under-twist-small: skein-canvas(size-factor: small-scale-factor, {
    import cetz.draw: *
    draw-strand(size-factor: small-scale-factor, { hobby((-1.5, +1), (-1, +1), (0.5, 0), (0.1, -1), (0, -1)) })
    draw-strand(size-factor: small-scale-factor, { hobby((1.5, +1), (1, +1), (-0.5, 0), (-0.1, -1), (0, -1)) })
  }),
)

#let skein-generic(kind: "over", direction: (+1, +1), arrows: (true, true), styles: ((:), (:)), size-factor: 1.0) = {
  skein-canvas(size-factor: size-factor, {
    import cetz.draw: *
    if kind == "over" {
      draw-strand(size-factor: size-factor, { line((-1, -1), (1, 1)) }, style: styles.at(1))
      draw-strand(size-factor: size-factor, { line((-1, 1), (1, -1)) }, style: styles.at(0))
    }
    if kind == "under" {
      draw-strand(size-factor: size-factor, { line((-1, 1), (1, -1)) }, style: styles.at(0))
      draw-strand(size-factor: size-factor, { line((-1, -1), (1, 1)) }, style: styles.at(1))
    }

    if arrows.at(0) {
      if direction.at(0) == +1 {
        line((1 - arrow-size, -1), (1, -1), (1, -1 + arrow-size), ..styles.at(0))
      } else {
        line((-1, 1 - arrow-size), (-1, 1), (-1 + arrow-size, 1), ..styles.at(0))
      }
    }
    if arrows.at(1) {
      if direction.at(1) == +1 {
        line((1 - arrow-size, 1), (1, 1), (1, 1 - arrow-size), ..styles.at(1))
      } else {
        line((-1, -1 + arrow-size), (-1, -1), (-1 + arrow-size, -1), ..styles.at(1))
      }
    }
  })
}
