// Copyright 2023 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:collection/collection.dart';
import 'package:node_interop/js.dart';
import 'package:sass/src/js/immutable.dart';
import 'package:sass/src/js/utils.dart';

import '../../value.dart';
import '../reflection.dart';

/// Check that [arg] is a valid argument to a calculation function.
void _assertCalculationValue(Object arg) => switch (arg) {
      SassNumber() ||
      SassString(hasQuotes: false) ||
      SassCalculation() ||
      CalculationOperation() ||
      CalculationInterpolation() =>
        null,
      _ => jsThrow(
          JsError(
            'Argument `$arg` must be one of SassNumber, unquoted SassString, '
            'SassCalculation, CalculationOperation, CalculationInterpolation',
          ),
        ),
    };

/// Check that [arg] is an unquoted string or interpolation.
bool _isValidClampArg(Object? arg) => switch (arg) {
      CalculationInterpolation() || SassString(hasQuotes: false) => true,
      _ => false,
    };

/// The JavaScript `SassCalculation` class.
final JSClass calculationClass = () {
  var jsClass = createJSClass('sass.SassCalculation', (
    Object self, [
    Object? _,
  ]) {
    jsThrow(JsError("new sass.SassCalculation() isn't allowed"));
  });

  jsClass.defineStaticMethods({
    'calc': (Object argument) {
      _assertCalculationValue(argument);
      return SassCalculation.unsimplified('calc', [argument]);
    },
    'min': (Object arguments) {
      var argList = jsToDartList(arguments).cast<Object>();
      argList.forEach(_assertCalculationValue);
      return SassCalculation.unsimplified('min', argList);
    },
    'max': (Object arguments) {
      var argList = jsToDartList(arguments).cast<Object>();
      argList.forEach(_assertCalculationValue);
      return SassCalculation.unsimplified('max', argList);
    },
    'clamp': (Object min, [Object? value, Object? max]) {
      if ((value == null && !_isValidClampArg(min)) ||
          (max == null && ![min, value].any(_isValidClampArg))) {
        jsThrow(
          JsError(
            'Expected at least one SassString or '
            'CalculationInterpolation in `${[min, value, max].nonNulls}`',
          ),
        );
      }
      [min, value, max].nonNulls.forEach(_assertCalculationValue);
      return SassCalculation.unsimplified('clamp', [min, value, max].nonNulls);
    },
  });

  jsClass.defineMethods({
    'assertCalculation': (SassCalculation self, [String? name]) => self,
  });

  jsClass.defineGetters({
    // The `name` getter is included by default by `createJSClass`
    'arguments': (SassCalculation self) => ImmutableList(self.arguments),
  });

  getJSClass(
    SassCalculation.unsimplified('calc', [SassNumber(1)]),
  ).injectSuperclass(jsClass);
  return jsClass;
}();

/// The JavaScript `CalculationOperation` class.
final JSClass calculationOperationClass = () {
  var jsClass = createJSClass('sass.CalculationOperation', (
    Object self,
    String strOperator,
    Object left,
    Object right,
  ) {
    var operator = CalculationOperator.values.firstWhereOrNull(
      (value) => value.operator == strOperator,
    );
    if (operator == null) {
      jsThrow(JsError('Invalid operator: $strOperator'));
    }
    _assertCalculationValue(left);
    _assertCalculationValue(right);
    return SassCalculation.operateInternal(
      operator,
      left,
      right,
      inLegacySassFunction: null,
      simplify: false,
      warn: null,
    );
  });

  jsClass.defineMethods({
    'equals': (CalculationOperation self, Object other) => self == other,
    'hashCode': (CalculationOperation self) => self.hashCode,
  });

  jsClass.defineGetters({
    'operator': (CalculationOperation self) => self.operator.operator,
    'left': (CalculationOperation self) => self.left,
    'right': (CalculationOperation self) => self.right,
  });

  getJSClass(
    SassCalculation.operateInternal(
      CalculationOperator.plus,
      SassNumber(1),
      SassNumber(1),
      inLegacySassFunction: null,
      simplify: false,
      warn: null,
    ),
  ).injectSuperclass(jsClass);
  return jsClass;
}();

/// The JavaScript `CalculationInterpolation` class.
final JSClass calculationInterpolationClass = () {
  var jsClass = createJSClass(
    'sass.CalculationInterpolation',
    (Object self, String value) => CalculationInterpolation(value),
  );

  jsClass.defineMethods({
    'equals': (CalculationInterpolation self, Object other) => self == other,
    'hashCode': (CalculationInterpolation self) => self.hashCode,
  });

  jsClass.defineGetters({
    'value': (CalculationInterpolation self) => self.value,
  });

  getJSClass(CalculationInterpolation('')).injectSuperclass(jsClass);
  return jsClass;
}();
