import RuleRepository from "./RuleRepository";
import { querySelectorAllDeep } from 'query-selector-shadow-dom';
import Rule from "./Rule";
import Action from "../Action";

export default class RuleService {
    elementRules: Map<Element, Rule[]>;
    ruleRepository: RuleRepository;

    constructor() {
        this.ruleRepository = undefined;
        this.elementRules = new Map();
    }

    getEventsToHandle(): string[] {
        return [... new Set(this.ruleRepository.getRules().map((rule: Rule) => rule.event))];
    }

    loadRules(website) {
        let mappingList = website.mappingList;
        this.ruleRepository = new RuleRepository();
        mappingList.forEach((mapping) => {
            this.ruleRepository.addRule(mapping);
        });
    }

    attachRulesToElements() {
        if (this.ruleRepository) {
            this.ruleRepository.getRules().forEach((rule) => {
                let elements;
                if (rule.xpath){
                    let result = document.evaluate(rule.xpath, document, null, XPathResult.ANY_TYPE, null);
                    elements = [];
                    let node = result.iterateNext();
                    while (node) {
                        elements.push(node);
                        node = result.iterateNext();
                    }            
                } else {
                    elements = querySelectorAllDeep(rule.css);
                }
                for (const dom_element of elements) {
                    if (!this.elementRules.has(dom_element)) {
                        this.elementRules.set(dom_element, [])
                    }
                    let rulesOnElement = this.elementRules.get(dom_element);
                    rulesOnElement.push(rule)
                }
            })
        }
    }

    getMatchingRules(event, listenedElement): Promise<Rule[]> {
        return new Promise((resolve) => {
            const rules = this.elementRules.get(listenedElement)
            const matchingRules = [];
            if (rules) {
                for (const rule of rules) {
                    if (rule.match(event)) {
                        matchingRules.push(rule);
                    }
                }
            }
            resolve(matchingRules);
        })
    }

    getListenedElements(event) {
        const eventTarget = event.composedPath()[0];
        for (const [element] of this.elementRules) {
            if (eventTarget instanceof Node && element.contains(eventTarget)) {
                return element;
            }
        } 
    }

    actionToElements(action: Action) {
        const rules = this.ruleRepository.getRulesByPrefix(action.prefix);
        const elementSet = new Set();
        if (rules && rules.length > 0) {
            for (const rule of rules) {
                let elementsForRule = rule.actionToElements(action);
                elementsForRule.forEach(element => elementSet.add(element));
            }
        }
        return [...elementSet];
    }

}