import * as C from "@effect-ts/core/Collections/Immutable/Chunk";
import { pipe } from "@effect-ts/core/Function";
import * as O from "@effect-ts/core/Option";
import { matchTag, matchTag_ } from "@effect-ts/core/Utils";
import * as ClientHorizontalLayout from "../../RenderOptions/HorizontalLayout/index.mjs";
import * as MergeAction from "../MergeAction/index.mjs"; // -----------------------------------------------------------------------------
// Operations
// -----------------------------------------------------------------------------

/**
 * Creates a merge strategy to perform horizontal merging, based on the given
 * `RenderOptions`.
 *
 * @param options The `RenderOptions` used to build the `MergeStrategy`.
 * @return A `MergeStrategy` function that performs horizontal merging.
 */

export function mergeStrategy(options) {
  const chosenLayout = ClientHorizontalLayout.toInternalLayout_(options.horizontalLayout, options.font);
  return layoutToMergeStrategy_(chosenLayout, options.font.header.hardblank);
}
/**
 * Returns the merge strategy function for a given layout.
 */

function layoutToMergeStrategy_(layout, hardblank) {
  return matchTag_(layout, {
    FullWidth: () => fullWidthStrategy,
    HorizontalFitting: () => horizontalFittingStrategy,
    UniversalSmushing: () => universalHorizontalSmushingStrategy(hardblank),
    ControlledSmushing: _ => controlledHorizontalSmushingStrategy(hardblank, _.rules)
  });
}
/**
 * A `MergeStrategy` for the `FullWidth` horizontal layout.
 */


const fullWidthStrategy = () => () => new MergeAction.Stop();
/**
 * A `MergeStrategy` for the `HorizontalFitting` horizontal layout.
 */


const horizontalFittingStrategy = () => (a, b) => {
  if (b === " ") return new MergeAction.Continue({
    value: a
  });
  if (a === " ") return new MergeAction.Continue({
    value: b
  });
  return new MergeAction.Stop();
};
/**
 * A `MergeStrategy` for the `UniversalSmushing` horizontal layout.
 */


function universalHorizontalSmushingStrategy(hardblank) {
  return state => (a, b) => {
    if (b === " ") return new MergeAction.Continue({
      value: a
    });
    if (a === " ") return new MergeAction.Continue({
      value: b
    });
    if (skipSmushing(state)) return new MergeAction.Stop();

    if (b === hardblank) {
      return a === hardblank ? new MergeAction.Stop() : new MergeAction.CurrentLast({
        value: a
      });
    }

    return new MergeAction.CurrentLast({
      value: b
    });
  };
}
/**
 * A `MergeStrategy` for the `ControlledSmushing` horizontal layout.
 */


function controlledHorizontalSmushingStrategy(hardblank, rules) {
  return state => (a, b) => {
    if (b === " ") return new MergeAction.Continue({
      value: a
    });
    if (a === " ") return new MergeAction.Continue({
      value: b
    });
    if (skipSmushing(state)) return new MergeAction.Stop();
    return O.fold_(C.head(C.compact(C.map_(C.map_(rules, ruleToSmushingStrategy(hardblank)), f => f(a, b)))), () => new MergeAction.Stop(), value => new MergeAction.CurrentLast({
      value
    }));
  };
}
/**
 * A note in the original figlet source code states: "Disallows overlapping
 * if the previous character or the current character has a width of 1 or zero".
 * This is an undocumented behaviour.
 */


function skipSmushing(state) {
  return state.lastCharWidth <= 1 || state.currentCharWidth <= 1;
}
/**
 * Returns a smushing strategy function given the smushing rule.
 */


function ruleToSmushingStrategy(hardblank) {
  return matchTag({
    EqualCharacter: () => equalCharacterSmushingRule(hardblank),
    Underscore: () => underscoreSmushingRule,
    Hierarchy: () => hierarchySmushingRule,
    OppositePair: () => oppositePairSmushingRule,
    BigX: () => bigXSmushingRule,
    Hardblank: () => hardblankSmushingRule(hardblank)
  });
}
/**
 * Two sub-characters are smushed into a single sub-character if they are the
 * same. This rule does not smush hardblanks.
 */


function equalCharacterSmushingRule(hardblank) {
  return (a, b) => a === b && a !== hardblank ? O.some(a) : O.none;
}
/**
 * An underscore (`"_"`) will be replaced by any of: `"|", "/", "\", "[", "]",
 * "{", "}", "(", ")", "<", or ">"`.
 */


const underscoreSmushingRule = (a, b) => {
  const replaceWith = "|/\\[]{}()<>";
  if (a === "_" && replaceWith.includes(b)) return O.some(b);
  if (b === "_" && replaceWith.includes(a)) return O.some(a);
  return O.none;
};
/**
 * A hierarchy of six classes is used: `"|", "/\", "[]", "{}", "()", and "<>"`.
 * When two smushing sub-characters are from different classes, the one from
 * the latter class will be used.
 */


const hierarchySmushingRule = (a, b) => {
  const classes = C.from(["|", "/\\", "[]", "{}", "()", "<>"]);
  const aClass = C.indexWhere_(classes, _ => _.includes(a));
  const bClass = C.indexWhere_(classes, _ => _.includes(b));

  if (aClass >= 0 && bClass >= 0 && aClass !== bClass) {
    return aClass > bClass ? O.some(a) : O.some(b);
  }

  return O.none;
};
/**
 * Smushes opposing brackets (`"[]"` or `"]["`), braces (`"{}"` or `"}{"`) and
 * parentheses (`"()"` or `")("`) together, replacing any such pair with a
 * vertical bar (`"|"`).
 */


const oppositePairSmushingRule = (a, b) => {
  if (a === "{" && b === "}") return O.some("|");
  if (a === "}" && b === "{") return O.some("|");
  if (a === "[" && b === "]") return O.some("|");
  if (a === "]" && b === "[") return O.some("|");
  if (a === "(" && b === ")") return O.some("|");
  if (a === ")" && b === "(") return O.some("|");
  return O.none;
};
/**
 * Smushes `"/\"` into `"|"`, `"\/"` into `"Y"`, and `"><"` into `"X"`.
 *
 * Note that `"<>"` is not smushed in any way by this rule. The name "BIG X" is
 * historical; originally all three pairs were smushed into `"X"`.
 */


const bigXSmushingRule = (a, b) => {
  if (a === "/" && b === "\\") return O.some("|");
  if (a === "\\" && b === "/") return O.some("Y");
  if (a === ">" && b === "<") return O.some("X");
  return O.none;
};
/**
 * Smushes two hardblanks together, replacing them with a single hardblank.
 */


function hardblankSmushingRule(hardblank) {
  return (a, b) => a === hardblank && a === b ? O.some(a) : O.none;
}
//# sourceMappingURL=index.mjs.map