// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:source_span/source_span.dart';
import 'parser.dart';

part 'src/css_printer.dart';
part 'src/tree.dart';
part 'src/tree_base.dart';
part 'src/tree_printer.dart';

abstract class VisitorBase {
  dynamic visitCalcTerm(CalcTerm node);
  dynamic visitCssComment(CssComment node);
  dynamic visitCommentDefinition(CommentDefinition node);
  dynamic visitStyleSheet(StyleSheet node);
  dynamic visitNoOp(NoOp node);
  dynamic visitTopLevelProduction(TopLevelProduction node);
  dynamic visitDirective(Directive node);
  dynamic visitDocumentDirective(DocumentDirective node);
  dynamic visitSupportsDirective(SupportsDirective node);
  dynamic visitSupportsConditionInParens(SupportsConditionInParens node);
  dynamic visitSupportsNegation(SupportsNegation node);
  dynamic visitSupportsConjunction(SupportsConjunction node);
  dynamic visitSupportsDisjunction(SupportsDisjunction node);
  dynamic visitViewportDirective(ViewportDirective node);
  dynamic visitMediaExpression(MediaExpression node);
  dynamic visitMediaQuery(MediaQuery node);
  dynamic visitMediaDirective(MediaDirective node);
  dynamic visitHostDirective(HostDirective node);
  dynamic visitPageDirective(PageDirective node);
  dynamic visitCharsetDirective(CharsetDirective node);
  dynamic visitImportDirective(ImportDirective node);
  dynamic visitKeyFrameDirective(KeyFrameDirective node);
  dynamic visitKeyFrameBlock(KeyFrameBlock node);
  dynamic visitFontFaceDirective(FontFaceDirective node);
  dynamic visitStyletDirective(StyletDirective node);
  dynamic visitNamespaceDirective(NamespaceDirective node);
  dynamic visitVarDefinitionDirective(VarDefinitionDirective node);
  dynamic visitMixinDefinition(MixinDefinition node);
  dynamic visitMixinRulesetDirective(MixinRulesetDirective node);
  dynamic visitMixinDeclarationDirective(MixinDeclarationDirective node);
  dynamic visitIncludeDirective(IncludeDirective node);
  dynamic visitContentDirective(ContentDirective node);

  dynamic visitRuleSet(RuleSet node);
  dynamic visitDeclarationGroup(DeclarationGroup node);
  dynamic visitMarginGroup(MarginGroup node);
  dynamic visitDeclaration(Declaration node);
  dynamic visitVarDefinition(VarDefinition node);
  dynamic visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node);
  dynamic visitExtendDeclaration(ExtendDeclaration node);
  dynamic visitSelectorGroup(SelectorGroup node);
  dynamic visitSelector(Selector node);
  dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node);
  dynamic visitSimpleSelector(SimpleSelector node);
  dynamic visitElementSelector(ElementSelector node);
  dynamic visitNamespaceSelector(NamespaceSelector node);
  dynamic visitAttributeSelector(AttributeSelector node);
  dynamic visitIdSelector(IdSelector node);
  dynamic visitClassSelector(ClassSelector node);
  dynamic visitPseudoClassSelector(PseudoClassSelector node);
  dynamic visitPseudoElementSelector(PseudoElementSelector node);
  dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node);
  dynamic visitPseudoElementFunctionSelector(
      PseudoElementFunctionSelector node);
  dynamic visitNegationSelector(NegationSelector node);
  dynamic visitSelectorExpression(SelectorExpression node);

  dynamic visitUnicodeRangeTerm(UnicodeRangeTerm node);
  dynamic visitLiteralTerm(LiteralTerm node);
  dynamic visitHexColorTerm(HexColorTerm node);
  dynamic visitNumberTerm(NumberTerm node);
  dynamic visitUnitTerm(UnitTerm node);
  dynamic visitLengthTerm(LengthTerm node);
  dynamic visitPercentageTerm(PercentageTerm node);
  dynamic visitEmTerm(EmTerm node);
  dynamic visitExTerm(ExTerm node);
  dynamic visitAngleTerm(AngleTerm node);
  dynamic visitTimeTerm(TimeTerm node);
  dynamic visitFreqTerm(FreqTerm node);
  dynamic visitFractionTerm(FractionTerm node);
  dynamic visitUriTerm(UriTerm node);
  dynamic visitResolutionTerm(ResolutionTerm node);
  dynamic visitChTerm(ChTerm node);
  dynamic visitRemTerm(RemTerm node);
  dynamic visitViewportTerm(ViewportTerm node);
  dynamic visitLineHeightTerm(LineHeightTerm node);
  dynamic visitFunctionTerm(FunctionTerm node);
  dynamic visitGroupTerm(GroupTerm node);
  dynamic visitItemTerm(ItemTerm node);
  dynamic visitIE8Term(IE8Term node);
  dynamic visitOperatorSlash(OperatorSlash node);
  dynamic visitOperatorComma(OperatorComma node);
  dynamic visitOperatorPlus(OperatorPlus node);
  dynamic visitOperatorMinus(OperatorMinus node);
  dynamic visitVarUsage(VarUsage node);

  dynamic visitExpressions(Expressions node);
  dynamic visitBinaryExpression(BinaryExpression node);
  dynamic visitUnaryExpression(UnaryExpression node);

  dynamic visitIdentifier(Identifier node);
  dynamic visitWildcard(Wildcard node);
  dynamic visitThisOperator(ThisOperator node);
  dynamic visitNegation(Negation node);

  dynamic visitDartStyleExpression(DartStyleExpression node);
  dynamic visitFontExpression(FontExpression node);
  dynamic visitBoxExpression(BoxExpression node);
  dynamic visitMarginExpression(MarginExpression node);
  dynamic visitBorderExpression(BorderExpression node);
  dynamic visitHeightExpression(HeightExpression node);
  dynamic visitPaddingExpression(PaddingExpression node);
  dynamic visitWidthExpression(WidthExpression node);
}

/// Base vistor class for the style sheet AST.
class Visitor implements VisitorBase {
  /// Helper function to walk a list of nodes.
  void _visitNodeList(List<TreeNode> list) {
    // Don't use iterable otherwise the list can't grow while using Visitor.
    // It certainly can't have items deleted before the index being iterated
    // but items could be added after the index.
    for (var index = 0; index < list.length; index++) {
      list[index].visit(this);
    }
  }

  dynamic visitTree(StyleSheet tree) => visitStyleSheet(tree);

  @override
  dynamic visitStyleSheet(StyleSheet ss) {
    _visitNodeList(ss.topLevels);
  }

  @override
  dynamic visitNoOp(NoOp node) {}

  @override
  dynamic visitTopLevelProduction(TopLevelProduction node) {}

  @override
  dynamic visitDirective(Directive node) {}

  @override
  dynamic visitCalcTerm(CalcTerm node) {
    visitLiteralTerm(node);
    visitLiteralTerm(node.expr);
  }

  @override
  dynamic visitCssComment(CssComment node) {}

  @override
  dynamic visitCommentDefinition(CommentDefinition node) {}

  @override
  dynamic visitMediaExpression(MediaExpression node) {
    visitExpressions(node.exprs);
  }

  @override
  dynamic visitMediaQuery(MediaQuery node) {
    for (var mediaExpr in node.expressions) {
      visitMediaExpression(mediaExpr);
    }
  }

  @override
  dynamic visitDocumentDirective(DocumentDirective node) {
    _visitNodeList(node.functions);
    _visitNodeList(node.groupRuleBody);
  }

  @override
  dynamic visitSupportsDirective(SupportsDirective node) {
    node.condition!.visit(this);
    _visitNodeList(node.groupRuleBody);
  }

  @override
  dynamic visitSupportsConditionInParens(SupportsConditionInParens node) {
    node.condition!.visit(this);
  }

  @override
  dynamic visitSupportsNegation(SupportsNegation node) {
    node.condition.visit(this);
  }

  @override
  dynamic visitSupportsConjunction(SupportsConjunction node) {
    _visitNodeList(node.conditions);
  }

  @override
  dynamic visitSupportsDisjunction(SupportsDisjunction node) {
    _visitNodeList(node.conditions);
  }

  @override
  dynamic visitViewportDirective(ViewportDirective node) {
    node.declarations.visit(this);
  }

  @override
  dynamic visitMediaDirective(MediaDirective node) {
    _visitNodeList(node.mediaQueries);
    _visitNodeList(node.rules);
  }

  @override
  dynamic visitHostDirective(HostDirective node) {
    _visitNodeList(node.rules);
  }

  @override
  dynamic visitPageDirective(PageDirective node) {
    for (var declGroup in node._declsMargin) {
      if (declGroup is MarginGroup) {
        visitMarginGroup(declGroup);
      } else {
        visitDeclarationGroup(declGroup);
      }
    }
  }

  @override
  dynamic visitCharsetDirective(CharsetDirective node) {}

  @override
  dynamic visitImportDirective(ImportDirective node) {
    for (var mediaQuery in node.mediaQueries) {
      visitMediaQuery(mediaQuery);
    }
  }

  @override
  dynamic visitKeyFrameDirective(KeyFrameDirective node) {
    visitIdentifier(node.name!);
    _visitNodeList(node._blocks);
  }

  @override
  dynamic visitKeyFrameBlock(KeyFrameBlock node) {
    visitExpressions(node._blockSelectors);
    visitDeclarationGroup(node._declarations);
  }

  @override
  dynamic visitFontFaceDirective(FontFaceDirective node) {
    visitDeclarationGroup(node._declarations);
  }

  @override
  dynamic visitStyletDirective(StyletDirective node) {
    _visitNodeList(node.rules);
  }

  @override
  dynamic visitNamespaceDirective(NamespaceDirective node) {}

  @override
  dynamic visitVarDefinitionDirective(VarDefinitionDirective node) {
    visitVarDefinition(node.def);
  }

  @override
  dynamic visitMixinRulesetDirective(MixinRulesetDirective node) {
    _visitNodeList(node.rulesets);
  }

  @override
  dynamic visitMixinDefinition(MixinDefinition node) {}

  @override
  dynamic visitMixinDeclarationDirective(MixinDeclarationDirective node) {
    visitDeclarationGroup(node.declarations);
  }

  @override
  dynamic visitIncludeDirective(IncludeDirective node) {
    for (var index = 0; index < node.args.length; index++) {
      var param = node.args[index];
      _visitNodeList(param);
    }
  }

  @override
  dynamic visitContentDirective(ContentDirective node) {
    // TODO(terry): TBD
  }

  @override
  dynamic visitRuleSet(RuleSet node) {
    visitSelectorGroup(node.selectorGroup!);
    visitDeclarationGroup(node.declarationGroup);
  }

  @override
  dynamic visitDeclarationGroup(DeclarationGroup node) {
    _visitNodeList(node.declarations);
  }

  @override
  dynamic visitMarginGroup(MarginGroup node) => visitDeclarationGroup(node);

  @override
  dynamic visitDeclaration(Declaration node) {
    visitIdentifier(node._property!);
    if (node.expression != null) node.expression!.visit(this);
  }

  @override
  dynamic visitVarDefinition(VarDefinition node) {
    visitIdentifier(node._property!);
    if (node.expression != null) node.expression!.visit(this);
  }

  @override
  dynamic visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
    visitIncludeDirective(node.include);
  }

  @override
  dynamic visitExtendDeclaration(ExtendDeclaration node) {
    _visitNodeList(node.selectors);
  }

  @override
  dynamic visitSelectorGroup(SelectorGroup node) {
    _visitNodeList(node.selectors);
  }

  @override
  dynamic visitSelector(Selector node) {
    _visitNodeList(node.simpleSelectorSequences);
  }

  @override
  dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node) {
    node.simpleSelector.visit(this);
  }

  @override
  dynamic visitSimpleSelector(SimpleSelector node) =>
      (node._name as TreeNode).visit(this);

  @override
  dynamic visitNamespaceSelector(NamespaceSelector node) {
    if (node._namespace != null) (node._namespace as TreeNode).visit(this);
    if (node.nameAsSimpleSelector != null) {
      node.nameAsSimpleSelector!.visit(this);
    }
  }

  @override
  dynamic visitElementSelector(ElementSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitAttributeSelector(AttributeSelector node) {
    visitSimpleSelector(node);
  }

  @override
  dynamic visitIdSelector(IdSelector node) => visitSimpleSelector(node);

  @override
  dynamic visitClassSelector(ClassSelector node) => visitSimpleSelector(node);

  @override
  dynamic visitPseudoClassSelector(PseudoClassSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitPseudoElementSelector(PseudoElementSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitPseudoElementFunctionSelector(
          PseudoElementFunctionSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitNegationSelector(NegationSelector node) =>
      visitSimpleSelector(node);

  @override
  dynamic visitSelectorExpression(SelectorExpression node) {
    _visitNodeList(node.expressions);
  }

  @override
  dynamic visitUnicodeRangeTerm(UnicodeRangeTerm node) {}

  @override
  dynamic visitLiteralTerm(LiteralTerm node) {}

  @override
  dynamic visitHexColorTerm(HexColorTerm node) {}

  @override
  dynamic visitNumberTerm(NumberTerm node) {}

  @override
  dynamic visitUnitTerm(UnitTerm node) {}

  @override
  dynamic visitLengthTerm(LengthTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitPercentageTerm(PercentageTerm node) {
    visitLiteralTerm(node);
  }

  @override
  dynamic visitEmTerm(EmTerm node) {
    visitLiteralTerm(node);
  }

  @override
  dynamic visitExTerm(ExTerm node) {
    visitLiteralTerm(node);
  }

  @override
  dynamic visitAngleTerm(AngleTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitTimeTerm(TimeTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitFreqTerm(FreqTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitFractionTerm(FractionTerm node) {
    visitLiteralTerm(node);
  }

  @override
  dynamic visitUriTerm(UriTerm node) {
    visitLiteralTerm(node);
  }

  @override
  dynamic visitResolutionTerm(ResolutionTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitChTerm(ChTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitRemTerm(RemTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitViewportTerm(ViewportTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitLineHeightTerm(LineHeightTerm node) {
    visitUnitTerm(node);
  }

  @override
  dynamic visitFunctionTerm(FunctionTerm node) {
    visitLiteralTerm(node);
    visitExpressions(node._params);
  }

  @override
  dynamic visitGroupTerm(GroupTerm node) {
    for (var term in node._terms) {
      term.visit(this);
    }
  }

  @override
  dynamic visitItemTerm(ItemTerm node) {
    visitNumberTerm(node);
  }

  @override
  dynamic visitIE8Term(IE8Term node) {}

  @override
  dynamic visitOperatorSlash(OperatorSlash node) {}

  @override
  dynamic visitOperatorComma(OperatorComma node) {}

  @override
  dynamic visitOperatorPlus(OperatorPlus node) {}

  @override
  dynamic visitOperatorMinus(OperatorMinus node) {}

  @override
  dynamic visitVarUsage(VarUsage node) {
    _visitNodeList(node.defaultValues);
  }

  @override
  dynamic visitExpressions(Expressions node) {
    _visitNodeList(node.expressions);
  }

  @override
  dynamic visitBinaryExpression(BinaryExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitUnaryExpression(UnaryExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitIdentifier(Identifier node) {}

  @override
  dynamic visitWildcard(Wildcard node) {}

  @override
  dynamic visitThisOperator(ThisOperator node) {}

  @override
  dynamic visitNegation(Negation node) {}

  @override
  dynamic visitDartStyleExpression(DartStyleExpression node) {}

  @override
  dynamic visitFontExpression(FontExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitBoxExpression(BoxExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitMarginExpression(MarginExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitBorderExpression(BorderExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitHeightExpression(HeightExpression node) {
    // TODO(terry): TB
    throw UnimplementedError();
  }

  @override
  dynamic visitPaddingExpression(PaddingExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }

  @override
  dynamic visitWidthExpression(WidthExpression node) {
    // TODO(terry): TBD
    throw UnimplementedError();
  }
}
