{
  "type": "object",
  "required": [
    "telemetryId",
    "searchPageRegexp",
    "queryParamName",
    "queryParamNames"
  ],
  "properties": {
    "telemetryId": {
      "type": "string",
      "title": "Telemetry Id",
      "description": "The telemetry identifier for the provider.",
      "pattern": "^[a-z0-9-._]*$"
    },
    "searchPageMatches": {
      "type": "array",
      "title": "Search Page Matches",
      "description": "An array containing match expressions used to match on URLs.",
      "items": {
        "type": "string"
      }
    },
    "searchPageRegexp": {
      "type": "string",
      "title": "Search Page Regular Expression",
      "description": "A regular expression which matches the search page of the provider."
    },
    "queryParamName": {
      "type": "string",
      "title": "Search Query Parameter Name",
      "description": "The name of the query parameter for the user's search string. This is deprecated, in preference to queryParamNames, but still defined for older clients (pre Firefox 121)."
    },
    "queryParamNames": {
      "type": "array",
      "title": "Search Query Parameter Names",
      "description": "An array of query parameters that may be used for the user's search string.",
      "items": {
        "type": "string"
      }
    },
    "codeParamName": {
      "type": "string",
      "title": "Partner Code Parameter Name",
      "description": "The name of the query parameter for the partner code."
    },
    "taggedCodes": {
      "type": "array",
      "title": "Partner Codes",
      "description": "An array of partner codes to match against the parameters in the url. Matching these codes will report the SERP as tagged.",
      "items": {
        "type": "string",
        "pattern": "^[a-zA-Z0-9-._]*$"
      }
    },
    "expectedOrganicCodes": {
      "type": "array",
      "title": "Expected Organic Codes",
      "description": "An array of partner codes to match against the parameters in the url. Matching these codes will report the SERP as organic:none which means the user has done a search through the search engine's website rather than through SAP.",
      "items": {
        "type": "string",
        "pattern": "^[a-zA-Z0-9-._]*$"
      }
    },
    "organicCodes": {
      "type": "array",
      "title": "Organic Codes",
      "description": "An array of partner codes to match against the parameters in the url. Matching these codes will report the SERP as organic:<partner code>, which means the search was performed organically rather than through a SAP.",
      "items": {
        "type": "string",
        "pattern": "^[a-zA-Z0-9-._]*$"
      }
    },
    "followOnParamNames": {
      "type": "array",
      "title": "Follow-on Search Parameter Names",
      "description": "An array of query parameter names that are used when a follow-on search occurs.",
      "items": {
        "type": "string",
        "pattern": "^[a-z0-9-._]*$"
      }
    },
    "followOnCookies": {
      "type": "array",
      "title": "Follow-on Cookies",
      "description": "An array of cookie details that are used to identify follow-on searches.",
      "items": {
        "type": "object",
        "properties": {
          "extraCodeParamName": {
            "type": "string",
            "description": "The query parameter name in the URL that indicates this might be a follow-on search.",
            "pattern": "^[a-z0-9-._]*$"
          },
          "extraCodePrefixes": {
            "type": "array",
            "description": "Possible values for the query parameter in the URL that indicates this might be a follow-on search.",
            "items": {
              "type": "string",
              "pattern": "^[a-zA-Z0-9-._]*$"
            }
          },
          "host": {
            "type": "string",
            "description": "The hostname on which the cookie is stored.",
            "pattern": "^[a-z0-9-._]*$"
          },
          "name": {
            "type": "string",
            "description": "The name of the cookie to check.",
            "pattern": "^[a-zA-Z0-9-._]*$"
          },
          "codeParamName": {
            "type": "string",
            "description": "The name of parameter within the cookie.",
            "pattern": "^[a-zA-Z0-9-._]*$"
          }
        }
      }
    },
    "searchMode": {
      "type": "object",
      "title": "Search Mode Param Mapping",
      "description": "A map of param names to search modes, e.g. image_search. Default if not specified is 'normal_search'.",
      "properties": {},
      "additionalProperties": {
        "enum": ["image_search"]
      }
    },
    "extraAdServersRegexps": {
      "type": "array",
      "title": "Extra Ad Server Regular Expressions",
      "description": "An array of regular expressions that match URLs of potential ad servers.",
      "items": {
        "type": "string"
      }
    },
    "adServerAttributes": {
      "type": "array",
      "title": "Ad Server Attributes",
      "description": "An array of strings that potentially match data-attribute keys of anchors.",
      "items": {
        "type": "string"
      }
    },
    "components": {
      "type": "array",
      "title": "Components",
      "description": "An array of components that could be on the SERP.",
      "items": {
        "required": ["type"],
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "The type of component the anchor or DOM element should belong to.",
            "pattern": "^[a-z](?:_?[a-z])*$"
          },
          "included": {
            "type": "object",
            "description": "Conditions that should be fulfilled.",
            "properties": {
              "parent": {
                "title": "Parent",
                "description": "The DOM element that should only contain elements applicable to a single component type.",
                "type": "object",
                "properties": {
                  "selector": {
                    "description": "If topDown is true for this component, then this will be the value used in querySelectorAll(). Otherwise, it will be the value to in closest() from the context of an anchor.",
                    "type": "string"
                  },
                  "eventListeners": {
                    "$ref": "#/definitions/eventListeners"
                  },
                  "skipCount": {
                    "$ref": "#/definitions/skipCount"
                  }
                },
                "required": ["selector"]
              },
              "children": {
                "type": "array",
                "title": "Children",
                "description": "Child DOM elements of the parent. Optional.",
                "items": {
                  "type": "object",
                  "properties": {
                    "selector": {
                      "type": "string",
                      "description": "The selector to use querySelectorAll from the context of the parent."
                    },
                    "type": {
                      "type": "string",
                      "description": "The component type to use if this child is present.",
                      "pattern": "^[a-z](?:_?[a-z])*$"
                    },
                    "countChildren": {
                      "type": "boolean",
                      "description": "Whether we should count all instances of the child element instead of anchor links found inside of the parent. Defaults to false."
                    },
                    "eventListeners": {
                      "$ref": "#/definitions/eventListeners"
                    },
                    "skipCount": {
                      "$ref": "#/definitions/skipCount"
                    }
                  },
                  "required": ["selector"]
                }
              },
              "related": {
                "type": "object",
                "properties": {
                  "selector": {
                    "type": "string",
                    "description": "The selector to use querySelectorAll from the context of the parent. Any elements specified will have their click events registered and categorized as expanded unless explicitly overwritten in SearchSERPTelemetryChild."
                  }
                },
                "required": ["selector"]
              }
            },
            "required": ["parent"]
          },
          "excluded": {
            "type": "object",
            "description": "Conditions that should not be included.",
            "properties": {
              "parent": {
                "type": "object",
                "properties": {
                  "selector": {
                    "type": "string",
                    "description": "The root DOM element that shouldn't be a parent from the context of the anchor being inspected."
                  }
                },
                "required": ["selector"]
              }
            }
          },
          "default": {
            "type": "boolean",
            "description": "Whether this component should be the fallback option if a link was included in both ad-related regular expressions as well as regular expressions matching non-ad elements but couldn't be categorized. Defaults to false."
          },
          "topDown": {
            "type": "boolean",
            "description": "Whether the component should be found first by using document.querySelectorAll on the parent selector definition. Defaults to false."
          },
          "dependentRequired": {
            "topDown": ["included"]
          }
        }
      }
    },
    "ignoreLinkRegexps": {
      "type": "array",
      "title": "Ignore links matching regular expressions",
      "description": "Regular expressions matching links that should be ignored by the network observer.",
      "items": {
        "type": "string",
        "description": "The matching regular expression."
      }
    },
    "nonAdsLinkQueryParamNames": {
      "type": "array",
      "title": "Non-ads link query param names",
      "description": "Query param names present in non-ads link that recover the link that will be redirected to.",
      "items": {
        "type": "string",
        "string": "The query param name to examine."
      }
    },
    "nonAdsLinkRegexps": {
      "type": "array",
      "title": "Non-ads link matching regular expressions",
      "description": "An array containing known patterns that match non-ad links from a search provider.",
      "items": {
        "type": "string",
        "description": "The matching regular expression."
      }
    },
    "shoppingTab": {
      "type": "object",
      "title": "Shopping Tab",
      "description": "Deprecated in Firefox 149.",
      "properties": {
        "selector": {
          "type": "string",
          "description": "The elements on the page to inspect for the shopping tab. Should be anchor elements."
        },
        "regexp": {
          "type": "string",
          "description": "The regular expression to match against a possible shopping tab. Must be provided if using this feature."
        },
        "inspectRegexpInSERP": {
          "type": "boolean",
          "description": "Whether the regexp should be used against hrefs the selector matches against."
        }
      },
      "required": ["selector", "regexp"]
    },
    "domainExtraction": {
      "type": "object",
      "title": "Domain Extraction",
      "description": "An array of methods for extracting domains from a SERP result.",
      "properties": {
        "ads": {
          "type": "array",
          "description": "An array of methods for extracting domains from ads.",
          "items": {
            "$ref": "#/definitions/extraction"
          }
        },
        "nonAds": {
          "type": "array",
          "description": "An array of methods for extracting domains from non-ads.",
          "items": {
            "$ref": "#/definitions/extraction"
          }
        }
      }
    },
    "accountCookies": {
      "type": "array",
      "title": "Account Cookies",
      "description": "An array of cookie details that are used to determine whether a client is signed in to a provider's account. NOTE: This property appears only in Firefox 127 but was renamed to `signedInCookies` for Firefox 128 and beyond. Therefore do not use this property. Please use `signedInCookies` instead.",
      "items": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string",
            "description": "The host associated with a given cookie."
          },
          "name": {
            "type": "string",
            "description": "The name associated with a given cookie."
          }
        }
      }
    },
    "signedInCookies": {
      "type": "array",
      "title": "Signed-in Cookies",
      "description": "An array of cookie details that are used to determine whether a client is signed in to a provider's account.",
      "items": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string",
            "description": "The host associated with a given cookie."
          },
          "name": {
            "type": "string",
            "description": "The name associated with a given cookie."
          }
        }
      }
    },
    "isSPA": {
      "type": "boolean",
      "title": "Is Single Page App",
      "description": "Whether the provider exhibits tendencies of a single page app, namely changes the entire contents of the page without having to reload."
    },
    "defaultPageQueryParam": {
      "type": "object",
      "title": "Default page query parameter",
      "properties": {
        "key": {
          "type": "string",
          "description": "The key corresponding to the query parameter that contains what type of search page is being shown."
        },
        "value": {
          "type": "string",
          "description": "The value corresponding to the query parameter that should be matched against."
        }
      },
      "required": ["key", "value"]
    },
    "pageTypeParam": {
      "type": "object",
      "title": "Page type param",
      "properties": {
        "enableSPAHandling": {
          "type": "boolean",
          "description": "Whether a given SERP should be processed using the logic for single-page apps."
        },
        "keys": {
          "type": "array",
          "description": "An array of strings representing the key(s) corresponding to the query parameter(s) that contains what type of search page is being shown."
        },
        "pageTypes": {
          "type": "array",
          "description": "A possible page type (ex: 'web', 'shopping' or 'images').",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "The name of the page type."
              },
              "values": {
                "type": "array",
                "description": "An array of strings representing the possible page types."
              },
              "target": {
                "type": "string",
                "description": "The target to be recorded in telemetry."
              },
              "enabled": {
                "type": "boolean",
                "description": "Whether we should track this page type."
              },
              "isDefault": {
                "type": "boolean",
                "description": "Whether this page type represents a default SERP. We fall back to this property in case, upon a page load, there is a delay in adding the page type param to the URL."
              }
            },
            "required": ["name", "values", "enabled"]
          }
        }
      },
      "required": ["keys", "pageTypes"]
    },
    "subframes": {
      "title": "Subframes",
      "description": "An array of methods for determining if a sponsored subframe exists.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/subframe"
      }
    },
    "impressionAttributes": {
      "type": "array",
      "title": "Impression Attributes",
      "description": "An array of attributes to check and record for SERP impressions. Only supported in Firefox 149 onwards.",
      "items": {
        "type": "object",
        "required": ["key"],
        "properties": {
          "key": {
            "type": "string",
            "description": "The identifier for this impression attribute."
          },
          "url": {
            "type": "object",
            "description": "URL-based matching.",
            "properties": {
              "regexp": {
                "type": "string",
                "description": "A regular expression to match against the page URL."
              }
            }
          },
          "element": {
            "type": "object",
            "description": "DOM element-based matching criteria.",
            "properties": {
              "selector": {
                "type": "string",
                "description": "The CSS selector to use querySelectorAll from the context of the SERP."
              },
              "attributeName": {
                "type": "string",
                "description": "The attribute name to inspect on the selected elements. If not provided, the presence of the element is used."
              },
              "regexp": {
                "type": "string",
                "description": "A regular expression to match against the attribute's value."
              },
              "component": {
                "type": "object",
                "description": "Component information for the element.",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "The type of component the element belongs to.",
                    "pattern": "^[a-z](?:_?[a-z])*$"
                  },
                  "countImpressions": {
                    "type": "boolean",
                    "description": "Whether to count impressions for this component when the element is found."
                  }
                },
                "required": ["type"]
              }
            },
            "required": ["selector"]
          },
          "value": {
            "type": "string",
            "description": "A value to set for the attribute automatically. Useful if the attribute isn't available for the provider."
          }
        }
      }
    }
  },
  "definitions": {
    "eventListener": {
      "title": "Event Listener",
      "type": "object",
      "description": "Event listeners attached to a component.",
      "properties": {
        "eventType": {
          "title": "Event Type",
          "description": "The type of event to listen for. Custom events, especially those with special logic like keydownEnter, can be used if the Desktop code has been updated.",
          "type": "string",
          "pattern": "^[a-z][A-Za-z]*$"
        },
        "target": {
          "title": "Target",
          "description": "The component type to report when the event is triggered. Uses the child component type (if exists), otherwise uses the parent component type.",
          "type": "string",
          "pattern": "^[a-z](?:_?[a-z])*$"
        },
        "action": {
          "title": "Action",
          "description": "The action to report when the event is triggered. If the event type is 'click', defaults to clicked. Otherwise, this should be provided.",
          "type": "string",
          "pattern": "^[a-z](?:_?[a-z])*$"
        }
      },
      "required": ["eventType"]
    },
    "eventListeners": {
      "title": "Event Listeners",
      "description": "An array of Event Listeners to apply to elements.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/eventListener"
      }
    },
    "extraction": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "selectors": {
              "type": "string",
              "description": "The query to inspect all elements on the SERP."
            },
            "method": {
              "enum": ["dataAttribute"],
              "description": "The extraction method used for the query."
            },
            "options": {
              "type": "object",
              "properties": {
                "dataAttributeKey": {
                  "type": "string",
                  "description": "The data attribute key that will be looked up in order to retrieve its data attribute value."
                }
              },
              "required": ["dataAttributeKey"]
            }
          },
          "required": ["selectors", "method", "options"]
        },
        {
          "type": "object",
          "properties": {
            "selectors": {
              "type": "string",
              "description": "The query to use to inspect all elements on the SERP."
            },
            "method": {
              "enum": ["href"],
              "description": "The extraction method to use for the query."
            },
            "options": {
              "type": "object",
              "properties": {
                "queryParamKey": {
                  "type": "string",
                  "description": "The query parameter key to inspect in the href."
                },
                "queryParamValueIsHref": {
                  "type": "boolean",
                  "description": "Whether the query param value is expected to contain an href."
                }
              },
              "required": ["queryParamKey"]
            }
          },
          "required": ["selectors", "method"]
        },
        {
          "type": "object",
          "properties": {
            "selectors": {
              "type": "string",
              "description": "The query to use to inspect all elements on the SERP."
            },
            "method": {
              "enum": ["textContent"],
              "description": "The extraction method to use for the query."
            }
          },
          "required": ["selectors", "method"]
        }
      ]
    },
    "skipCount": {
      "title": "Skip Count",
      "description": "Whether to skip reporting of the count of these elements to ad_impressions. Defaults to false.",
      "type": "boolean"
    },
    "subframe": {
      "title": "Subframe",
      "description": "Ways to look for sponsored subframes",
      "type": "object",
      "properties": {
        "inspectRegexpInParent": {
          "type": "boolean",
          "description": "Whether the regexp should be used to inspect network events in the parent process."
        },
        "inspectRegexpInSERP": {
          "type": "boolean",
          "description": "Whether the regexp should be used to inspect subframes in the content process."
        },
        "regexp": {
          "type": "string",
          "description": "A regular expression that matches subframes that hold sponsored content."
        }
      }
    }
  }
}
